@downcity/agent 1.1.267 → 1.1.270
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/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +1 -2
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/services/ExecutorRecoveryPolicy.d.ts +0 -1
- package/bin/executor/services/ExecutorRecoveryPolicy.d.ts.map +1 -1
- package/bin/executor/services/ExecutorRecoveryPolicy.js +2 -17
- package/bin/executor/services/ExecutorRecoveryPolicy.js.map +1 -1
- package/bin/executor/types/SessionRecords.d.ts +5 -7
- package/bin/executor/types/SessionRecords.d.ts.map +1 -1
- package/bin/executor/types/SessionRecords.js +3 -4
- package/bin/executor/types/SessionRecords.js.map +1 -1
- package/bin/index.d.ts +1 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/session/SessionMessages.d.ts.map +1 -1
- package/bin/session/SessionMessages.js +14 -1
- package/bin/session/SessionMessages.js.map +1 -1
- package/bin/session/SessionSystem.js +6 -6
- package/bin/session/SessionSystem.js.map +1 -1
- package/bin/session/SessionTitle.d.ts.map +1 -1
- package/bin/session/SessionTitle.js +0 -6
- package/bin/session/SessionTitle.js.map +1 -1
- package/bin/session/SessionTurn.d.ts.map +1 -1
- package/bin/session/SessionTurn.js +0 -25
- package/bin/session/SessionTurn.js.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.d.ts +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.d.ts.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.js +153 -12
- package/bin/session/messages/SessionAssistantMessageWriter.js.map +1 -1
- package/bin/session/messages/SessionJsonValue.d.ts +3 -1
- package/bin/session/messages/SessionJsonValue.d.ts.map +1 -1
- package/bin/session/messages/SessionJsonValue.js +12 -0
- package/bin/session/messages/SessionJsonValue.js.map +1 -1
- package/bin/session/messages/SessionMessageCodec.d.ts.map +1 -1
- package/bin/session/messages/SessionMessageCodec.js +226 -19
- package/bin/session/messages/SessionMessageCodec.js.map +1 -1
- package/bin/types/sdk/AgentSessionTurn.d.ts +2 -1
- package/bin/types/sdk/AgentSessionTurn.d.ts.map +1 -1
- package/bin/types/session/SessionMessage.d.ts +87 -3
- package/bin/types/session/SessionMessage.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/agent-session-maintenance.test.mjs +7 -1
- package/scripts/executor-failure-result.test.mjs +81 -0
- package/scripts/openai-responses-continuation.test.mjs +35 -5
- package/scripts/session-messages.test.mjs +287 -1
- package/scripts/session-shell-approval.test.mjs +3 -3
- package/scripts/session-system-blocks.test.mjs +14 -1
- package/scripts/session-turn-failure.test.mjs +101 -0
- package/src/executor/core-engine/CoreEngineRunner.ts +1 -5
- package/src/executor/services/ExecutorRecoveryPolicy.ts +2 -23
- package/src/executor/types/SessionRecords.ts +5 -7
- package/src/index.ts +11 -0
- package/src/session/SessionMessages.ts +14 -1
- package/src/session/SessionSystem.ts +6 -6
- package/src/session/SessionTitle.ts +0 -6
- package/src/session/SessionTurn.ts +0 -29
- package/src/session/messages/SessionAssistantMessageWriter.ts +170 -12
- package/src/session/messages/SessionJsonValue.ts +12 -1
- package/src/session/messages/SessionMessageCodec.ts +226 -18
- package/src/types/sdk/AgentSessionTurn.ts +2 -1
- package/src/types/session/SessionMessage.ts +96 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -107,16 +107,15 @@ interface ExecutorRecoveryRunInput {
|
|
|
107
107
|
* 执行恢复与重试策略服务。
|
|
108
108
|
*/
|
|
109
109
|
export class ExecutorRecoveryPolicy {
|
|
110
|
-
private readonly session_id: string;
|
|
111
110
|
private readonly should_compact: ExecutorRecoveryPolicyOptions["should_compact"];
|
|
112
111
|
private readonly logger: Logger;
|
|
113
112
|
private retry_count = 0;
|
|
114
113
|
|
|
115
114
|
constructor(options: ExecutorRecoveryPolicyOptions) {
|
|
116
|
-
|
|
115
|
+
const session_id = String(options.session_id || "").trim();
|
|
117
116
|
this.should_compact = options.should_compact;
|
|
118
117
|
this.logger = options.logger;
|
|
119
|
-
if (!
|
|
118
|
+
if (!session_id) {
|
|
120
119
|
throw new Error("ExecutorRecoveryPolicy requires a non-empty session_id");
|
|
121
120
|
}
|
|
122
121
|
}
|
|
@@ -161,8 +160,6 @@ export class ExecutorRecoveryPolicy {
|
|
|
161
160
|
return this.build_failure_result({
|
|
162
161
|
error_text:
|
|
163
162
|
"Context length exceeded and retries failed. Please resend your question.",
|
|
164
|
-
fallback_text:
|
|
165
|
-
"Context length exceeded and retries failed. Please resend your question.",
|
|
166
163
|
run_context: input.run_context,
|
|
167
164
|
});
|
|
168
165
|
}
|
|
@@ -173,7 +170,6 @@ export class ExecutorRecoveryPolicy {
|
|
|
173
170
|
});
|
|
174
171
|
return this.build_failure_result({
|
|
175
172
|
error_text,
|
|
176
|
-
fallback_text: `Execution failed: ${error_text}`,
|
|
177
173
|
run_context: input.run_context,
|
|
178
174
|
});
|
|
179
175
|
}
|
|
@@ -185,11 +181,6 @@ export class ExecutorRecoveryPolicy {
|
|
|
185
181
|
*/
|
|
186
182
|
error_text: string;
|
|
187
183
|
|
|
188
|
-
/**
|
|
189
|
-
* fallback assistant 消息文本。
|
|
190
|
-
*/
|
|
191
|
-
fallback_text: string;
|
|
192
|
-
|
|
193
184
|
/**
|
|
194
185
|
* 当前显式运行上下文。
|
|
195
186
|
*/
|
|
@@ -198,18 +189,6 @@ export class ExecutorRecoveryPolicy {
|
|
|
198
189
|
return {
|
|
199
190
|
success: false,
|
|
200
191
|
error: input.error_text,
|
|
201
|
-
assistantMessage: {
|
|
202
|
-
id: `a:${this.session_id}:${Date.now()}`,
|
|
203
|
-
role: "assistant",
|
|
204
|
-
metadata: {
|
|
205
|
-
v: 1,
|
|
206
|
-
ts: Date.now(),
|
|
207
|
-
sessionId: this.session_id,
|
|
208
|
-
source: "egress",
|
|
209
|
-
kind: "normal",
|
|
210
|
-
},
|
|
211
|
-
parts: [{ type: "text", text: input.fallback_text }],
|
|
212
|
-
},
|
|
213
192
|
deferredPersistedUserMessages: [
|
|
214
193
|
...input.run_context.deferredPersistedUserMessages,
|
|
215
194
|
],
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* SessionRecord 类型定义。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - 这些类型会被 Store、Composer、compact、control UI、task runtime 共同复用。
|
|
5
|
+
* - SessionMessage 是唯一持久化事实,record 只作为 Executor、Composer 和旧 UI 的内部投影。
|
|
6
|
+
* - message record 继续使用 AI SDK `UIMessage`,action record 不属于 LLM 输入。
|
|
7
|
+
* - 这些类型不定义 canonical Session 存储结构。
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { UIMessage } from "ai";
|
|
@@ -15,9 +14,8 @@ import type { JsonObject } from "@/types/common/Json.js";
|
|
|
15
14
|
* Session message record 类别。
|
|
16
15
|
*
|
|
17
16
|
* 关键点(中文)
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* - compact 会把更早 message record 压缩为一条 `assistant` 摘要 record。
|
|
17
|
+
* - User / Assistant SessionMessage 会在执行边界临时投影为 message record。
|
|
18
|
+
* - Compact Summary 同样投影为一条内部 `assistant` record。
|
|
21
19
|
* - action 只用于 UI timeline,不进入 LLM 输入
|
|
22
20
|
*/
|
|
23
21
|
export type SessionMessageKind = "normal" | "summary";
|
package/src/index.ts
CHANGED
|
@@ -40,14 +40,25 @@ export type {
|
|
|
40
40
|
export type {
|
|
41
41
|
ListSessionMessagesInput,
|
|
42
42
|
SessionActionMessage,
|
|
43
|
+
SessionAssistantDataPart,
|
|
44
|
+
SessionAssistantDocumentSourcePart,
|
|
45
|
+
SessionAssistantFilePart,
|
|
43
46
|
SessionAssistantMessage,
|
|
44
47
|
SessionAssistantMessagePart,
|
|
48
|
+
SessionAssistantSourcePart,
|
|
49
|
+
SessionAssistantStepPart,
|
|
50
|
+
SessionAssistantTextPart,
|
|
45
51
|
SessionAssistantToolPart,
|
|
52
|
+
SessionAssistantUrlSourcePart,
|
|
46
53
|
SessionErrorMessage,
|
|
47
54
|
SessionMessage,
|
|
48
55
|
SessionMessagePage,
|
|
56
|
+
SessionUserDataPart,
|
|
57
|
+
SessionUserFilePart,
|
|
49
58
|
SessionUserMessage,
|
|
50
59
|
SessionUserMessagePart,
|
|
60
|
+
SessionUserTextPart,
|
|
61
|
+
SessionToolApprovalSnapshot,
|
|
51
62
|
} from "./types/session/SessionMessage.js";
|
|
52
63
|
export type {
|
|
53
64
|
SessionContextSnapshot,
|
|
@@ -695,7 +695,10 @@ export class SessionMessages {
|
|
|
695
695
|
await this.update_assistant_part_serialized(message_id, {
|
|
696
696
|
...tool.part,
|
|
697
697
|
state: "approval-required",
|
|
698
|
-
approval
|
|
698
|
+
approval: {
|
|
699
|
+
approval_id: approval.approval_id,
|
|
700
|
+
request: approval,
|
|
701
|
+
},
|
|
699
702
|
});
|
|
700
703
|
});
|
|
701
704
|
}
|
|
@@ -721,6 +724,16 @@ export class SessionMessages {
|
|
|
721
724
|
await this.update_assistant_part_serialized(message_id, {
|
|
722
725
|
...tool.part,
|
|
723
726
|
state: input.decision === "approved" ? "running" : "failed",
|
|
727
|
+
approval: {
|
|
728
|
+
...tool.part.approval,
|
|
729
|
+
approval_id: input.approval_id,
|
|
730
|
+
approved: input.decision === "approved",
|
|
731
|
+
...(input.decision === "expired"
|
|
732
|
+
? { reason: "Approval expired" }
|
|
733
|
+
: input.decision === "denied"
|
|
734
|
+
? { reason: "Approval denied" }
|
|
735
|
+
: {}),
|
|
736
|
+
},
|
|
724
737
|
...(input.decision === "approved"
|
|
725
738
|
? {}
|
|
726
739
|
: {
|
|
@@ -59,12 +59,12 @@ function create_session_system_block(
|
|
|
59
59
|
session: AgentSessionSystemSessionInfo,
|
|
60
60
|
): AgentSessionSystemBlock {
|
|
61
61
|
const content = [
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
62
|
+
"Current session context:",
|
|
63
|
+
`You are serving agent "${session.agentId}" in session "${session.sessionId}".`,
|
|
64
|
+
`The current project root is "${session.projectRoot}".`,
|
|
65
|
+
`This session was created at ${session.createdAt}, with ${session.timezone} as its reference timezone.`,
|
|
66
|
+
"This creation time is a stable reference for the session and does not represent the current time for every run.",
|
|
67
|
+
"If the user message provides a newer current time, a relative time, or other dynamic context, prioritize the dynamic information from the user message.",
|
|
68
68
|
].join("\n");
|
|
69
69
|
return {
|
|
70
70
|
source: "session",
|
|
@@ -215,12 +215,6 @@ async function generateSessionTitle(input: {
|
|
|
215
215
|
"",
|
|
216
216
|
input.firstUserText,
|
|
217
217
|
].join("\n"),
|
|
218
|
-
// 标题生成是一次性调用,不参与 Responses continuation。
|
|
219
|
-
providerOptions: {
|
|
220
|
-
openai: {
|
|
221
|
-
store: false,
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
218
|
onError: ({ error }) => {
|
|
225
219
|
observedStreamError = error;
|
|
226
220
|
},
|
|
@@ -241,10 +241,6 @@ export class SessionTurn {
|
|
|
241
241
|
turnId: turn_id,
|
|
242
242
|
text: "",
|
|
243
243
|
success: false,
|
|
244
|
-
assistantMessage: build_prompt_runtime_error_assistant_message({
|
|
245
|
-
session_id: this.session_id,
|
|
246
|
-
message,
|
|
247
|
-
}),
|
|
248
244
|
error: message,
|
|
249
245
|
};
|
|
250
246
|
active_turn.result = final_result;
|
|
@@ -291,10 +287,6 @@ export class SessionTurn {
|
|
|
291
287
|
turnId: turn_id,
|
|
292
288
|
text: "",
|
|
293
289
|
success: false,
|
|
294
|
-
assistantMessage: build_prompt_runtime_error_assistant_message({
|
|
295
|
-
session_id: this.session_id,
|
|
296
|
-
message: QUEUED_PROMPT_CANCELLED_MESSAGE,
|
|
297
|
-
}),
|
|
298
290
|
error: QUEUED_PROMPT_CANCELLED_MESSAGE,
|
|
299
291
|
};
|
|
300
292
|
cancelled_turn.result = final_result;
|
|
@@ -630,27 +622,6 @@ function is_assistant_content_chunk(type: string): boolean {
|
|
|
630
622
|
);
|
|
631
623
|
}
|
|
632
624
|
|
|
633
|
-
function build_prompt_runtime_error_assistant_message(input: {
|
|
634
|
-
session_id: string;
|
|
635
|
-
message: string;
|
|
636
|
-
}): SessionMessageRecordV1 {
|
|
637
|
-
return {
|
|
638
|
-
id: `a:${input.session_id}:${Date.now()}:${nanoid(6)}`,
|
|
639
|
-
role: "assistant",
|
|
640
|
-
metadata: {
|
|
641
|
-
v: 1,
|
|
642
|
-
ts: Date.now(),
|
|
643
|
-
sessionId: input.session_id,
|
|
644
|
-
source: "egress",
|
|
645
|
-
kind: "normal",
|
|
646
|
-
extra: {
|
|
647
|
-
note: "session_prompt_runtime_error",
|
|
648
|
-
},
|
|
649
|
-
},
|
|
650
|
-
parts: [{ type: "text", text: input.message }],
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
|
|
654
625
|
function create_deferred<T>(): SessionDeferred<T> {
|
|
655
626
|
let resolve!: (value: T) => void;
|
|
656
627
|
const promise = new Promise<T>((inner_resolve) => {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { UIMessageChunk } from "ai";
|
|
9
9
|
import type { SessionMessages } from "@/session/SessionMessages.js";
|
|
10
10
|
import {
|
|
11
|
+
to_session_json_object,
|
|
11
12
|
to_session_json_value,
|
|
12
13
|
to_session_provider_metadata,
|
|
13
14
|
} from "@/session/messages/SessionJsonValue.js";
|
|
@@ -15,6 +16,7 @@ import type {
|
|
|
15
16
|
SessionAssistantFilePart,
|
|
16
17
|
SessionAssistantMessage,
|
|
17
18
|
SessionAssistantMessagePart,
|
|
19
|
+
SessionAssistantTextPart,
|
|
18
20
|
SessionAssistantToolPart,
|
|
19
21
|
} from "@/types/session/SessionMessage.js";
|
|
20
22
|
import type { SessionToolInputReady } from "@/types/session/SessionTool.js";
|
|
@@ -24,7 +26,10 @@ import { generateId } from "@/utils/Id.js";
|
|
|
24
26
|
export class SessionAssistantMessageWriter {
|
|
25
27
|
readonly message_id: string;
|
|
26
28
|
private readonly recorder: SessionMessages;
|
|
27
|
-
private readonly pending_text_parts = new Map<
|
|
29
|
+
private readonly pending_text_parts = new Map<
|
|
30
|
+
string,
|
|
31
|
+
Pick<SessionAssistantTextPart, "type" | "provider_metadata">
|
|
32
|
+
>();
|
|
28
33
|
private readonly active_text_part_ids = new Map<string, string>();
|
|
29
34
|
private write_chain: Promise<void> = Promise.resolve();
|
|
30
35
|
private closed = false;
|
|
@@ -51,7 +56,10 @@ export class SessionAssistantMessageWriter {
|
|
|
51
56
|
const type = chunk.type === "text-start" ? "text" : "reasoning";
|
|
52
57
|
const part_id = this.resolve_text_part_id(type, chunk.id);
|
|
53
58
|
if (!current.parts.some((part) => part.part_id === part_id)) {
|
|
54
|
-
this.pending_text_parts.set(part_id,
|
|
59
|
+
this.pending_text_parts.set(part_id, {
|
|
60
|
+
type,
|
|
61
|
+
provider_metadata: to_session_provider_metadata(chunk.providerMetadata),
|
|
62
|
+
});
|
|
55
63
|
}
|
|
56
64
|
return;
|
|
57
65
|
}
|
|
@@ -59,7 +67,11 @@ export class SessionAssistantMessageWriter {
|
|
|
59
67
|
case "reasoning-delta": {
|
|
60
68
|
const type = chunk.type === "text-delta" ? "text" : "reasoning";
|
|
61
69
|
const part_id = this.resolve_text_part_id(type, chunk.id);
|
|
62
|
-
await this.ensure_text_part(
|
|
70
|
+
await this.ensure_text_part(
|
|
71
|
+
part_id,
|
|
72
|
+
type,
|
|
73
|
+
to_session_provider_metadata(chunk.providerMetadata),
|
|
74
|
+
);
|
|
63
75
|
await this.recorder.append_assistant_delta(
|
|
64
76
|
this.message_id,
|
|
65
77
|
part_id,
|
|
@@ -76,7 +88,12 @@ export class SessionAssistantMessageWriter {
|
|
|
76
88
|
if (!part_id) return;
|
|
77
89
|
const part = current.parts.find((item) => item.part_id === part_id);
|
|
78
90
|
if (part?.type === "text" || part?.type === "reasoning") {
|
|
79
|
-
|
|
91
|
+
const provider_metadata = to_session_provider_metadata(chunk.providerMetadata);
|
|
92
|
+
await this.upsert_part({
|
|
93
|
+
...part,
|
|
94
|
+
state: "done",
|
|
95
|
+
...(provider_metadata !== undefined ? { provider_metadata } : {}),
|
|
96
|
+
});
|
|
80
97
|
} else {
|
|
81
98
|
this.pending_text_parts.delete(part_id);
|
|
82
99
|
}
|
|
@@ -88,10 +105,14 @@ export class SessionAssistantMessageWriter {
|
|
|
88
105
|
const call_provider_metadata = to_session_provider_metadata(
|
|
89
106
|
chunk.providerMetadata,
|
|
90
107
|
);
|
|
108
|
+
const tool_metadata = to_session_json_object(chunk.toolMetadata);
|
|
91
109
|
if (tool) {
|
|
92
110
|
if (
|
|
93
111
|
call_provider_metadata === undefined &&
|
|
94
|
-
chunk.providerExecuted === undefined
|
|
112
|
+
chunk.providerExecuted === undefined &&
|
|
113
|
+
chunk.title === undefined &&
|
|
114
|
+
tool_metadata === undefined &&
|
|
115
|
+
chunk.dynamic === undefined
|
|
95
116
|
) return;
|
|
96
117
|
await this.upsert_tool(chunk.toolCallId, {
|
|
97
118
|
tool_name: tool.tool_name,
|
|
@@ -102,6 +123,9 @@ export class SessionAssistantMessageWriter {
|
|
|
102
123
|
...(chunk.providerExecuted !== undefined
|
|
103
124
|
? { provider_executed: chunk.providerExecuted }
|
|
104
125
|
: {}),
|
|
126
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
127
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
128
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
105
129
|
});
|
|
106
130
|
return;
|
|
107
131
|
}
|
|
@@ -115,6 +139,9 @@ export class SessionAssistantMessageWriter {
|
|
|
115
139
|
...(chunk.providerExecuted !== undefined
|
|
116
140
|
? { provider_executed: chunk.providerExecuted }
|
|
117
141
|
: {}),
|
|
142
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
143
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
144
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
118
145
|
});
|
|
119
146
|
return;
|
|
120
147
|
}
|
|
@@ -133,10 +160,14 @@ export class SessionAssistantMessageWriter {
|
|
|
133
160
|
const call_provider_metadata = to_session_provider_metadata(
|
|
134
161
|
chunk.providerMetadata,
|
|
135
162
|
);
|
|
163
|
+
const tool_metadata = to_session_json_object(chunk.toolMetadata);
|
|
136
164
|
if (tool && tool.state !== "input-streaming") {
|
|
137
165
|
if (
|
|
138
166
|
call_provider_metadata === undefined &&
|
|
139
|
-
chunk.providerExecuted === undefined
|
|
167
|
+
chunk.providerExecuted === undefined &&
|
|
168
|
+
chunk.title === undefined &&
|
|
169
|
+
tool_metadata === undefined &&
|
|
170
|
+
chunk.dynamic === undefined
|
|
140
171
|
) return;
|
|
141
172
|
await this.upsert_tool(chunk.toolCallId, {
|
|
142
173
|
tool_name: tool.tool_name,
|
|
@@ -147,6 +178,9 @@ export class SessionAssistantMessageWriter {
|
|
|
147
178
|
...(chunk.providerExecuted !== undefined
|
|
148
179
|
? { provider_executed: chunk.providerExecuted }
|
|
149
180
|
: {}),
|
|
181
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
182
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
183
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
150
184
|
});
|
|
151
185
|
return;
|
|
152
186
|
}
|
|
@@ -160,6 +194,9 @@ export class SessionAssistantMessageWriter {
|
|
|
160
194
|
...(chunk.providerExecuted !== undefined
|
|
161
195
|
? { provider_executed: chunk.providerExecuted }
|
|
162
196
|
: {}),
|
|
197
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
198
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
199
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
163
200
|
});
|
|
164
201
|
return;
|
|
165
202
|
}
|
|
@@ -167,17 +204,33 @@ export class SessionAssistantMessageWriter {
|
|
|
167
204
|
const call_provider_metadata = to_session_provider_metadata(
|
|
168
205
|
chunk.providerMetadata,
|
|
169
206
|
);
|
|
207
|
+
const tool_metadata = to_session_json_object(chunk.toolMetadata);
|
|
170
208
|
await this.upsert_tool(chunk.toolCallId, {
|
|
171
209
|
tool_name: chunk.toolName,
|
|
172
210
|
state: "failed",
|
|
173
211
|
input: to_session_json_value(chunk.input),
|
|
174
212
|
error: chunk.errorText,
|
|
213
|
+
...(chunk.input === undefined && "rawInput" in chunk && chunk.rawInput !== undefined
|
|
214
|
+
? { raw_input: to_session_json_value(chunk.rawInput) }
|
|
215
|
+
: {}),
|
|
175
216
|
...(call_provider_metadata !== undefined
|
|
176
217
|
? { call_provider_metadata }
|
|
177
218
|
: {}),
|
|
178
219
|
...(chunk.providerExecuted !== undefined
|
|
179
220
|
? { provider_executed: chunk.providerExecuted }
|
|
180
221
|
: {}),
|
|
222
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
223
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
224
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
case "tool-approval-request": {
|
|
229
|
+
const tool = this.find_tool(chunk.toolCallId);
|
|
230
|
+
await this.upsert_tool(chunk.toolCallId, {
|
|
231
|
+
tool_name: tool?.tool_name || "unknown",
|
|
232
|
+
state: "approval-required",
|
|
233
|
+
approval: { approval_id: chunk.approvalId },
|
|
181
234
|
});
|
|
182
235
|
return;
|
|
183
236
|
}
|
|
@@ -190,6 +243,7 @@ export class SessionAssistantMessageWriter {
|
|
|
190
243
|
const result_provider_metadata = to_session_provider_metadata(
|
|
191
244
|
chunk.providerMetadata,
|
|
192
245
|
);
|
|
246
|
+
const tool_metadata = to_session_json_object(chunk.toolMetadata);
|
|
193
247
|
await this.upsert_tool(chunk.toolCallId, {
|
|
194
248
|
tool_name: tool?.tool_name || "unknown",
|
|
195
249
|
state: "completed",
|
|
@@ -200,6 +254,11 @@ export class SessionAssistantMessageWriter {
|
|
|
200
254
|
...(chunk.providerExecuted !== undefined
|
|
201
255
|
? { provider_executed: chunk.providerExecuted }
|
|
202
256
|
: {}),
|
|
257
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
258
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
259
|
+
...(chunk.preliminary !== undefined
|
|
260
|
+
? { preliminary: chunk.preliminary }
|
|
261
|
+
: {}),
|
|
203
262
|
});
|
|
204
263
|
return;
|
|
205
264
|
}
|
|
@@ -208,6 +267,7 @@ export class SessionAssistantMessageWriter {
|
|
|
208
267
|
const result_provider_metadata = to_session_provider_metadata(
|
|
209
268
|
chunk.providerMetadata,
|
|
210
269
|
);
|
|
270
|
+
const tool_metadata = to_session_json_object(chunk.toolMetadata);
|
|
211
271
|
await this.upsert_tool(chunk.toolCallId, {
|
|
212
272
|
tool_name: tool?.tool_name || "unknown",
|
|
213
273
|
state: "failed",
|
|
@@ -218,6 +278,8 @@ export class SessionAssistantMessageWriter {
|
|
|
218
278
|
...(chunk.providerExecuted !== undefined
|
|
219
279
|
? { provider_executed: chunk.providerExecuted }
|
|
220
280
|
: {}),
|
|
281
|
+
...(tool_metadata !== undefined ? { tool_metadata } : {}),
|
|
282
|
+
...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
|
|
221
283
|
});
|
|
222
284
|
return;
|
|
223
285
|
}
|
|
@@ -227,6 +289,12 @@ export class SessionAssistantMessageWriter {
|
|
|
227
289
|
tool_name: tool?.tool_name || "unknown",
|
|
228
290
|
state: "failed",
|
|
229
291
|
error: "Tool output denied",
|
|
292
|
+
approval: {
|
|
293
|
+
...(tool?.approval || {}),
|
|
294
|
+
approval_id:
|
|
295
|
+
tool?.approval?.approval_id || `approval:${chunk.toolCallId}`,
|
|
296
|
+
approved: false,
|
|
297
|
+
},
|
|
230
298
|
});
|
|
231
299
|
return;
|
|
232
300
|
}
|
|
@@ -234,10 +302,73 @@ export class SessionAssistantMessageWriter {
|
|
|
234
302
|
await this.append_file_part({
|
|
235
303
|
media_type: chunk.mediaType,
|
|
236
304
|
url: chunk.url,
|
|
305
|
+
provider_metadata: to_session_provider_metadata(chunk.providerMetadata),
|
|
237
306
|
});
|
|
238
307
|
return;
|
|
239
|
-
|
|
308
|
+
case "source-url": {
|
|
309
|
+
const part_id = `source:${chunk.sourceId}`;
|
|
310
|
+
const current_part = current.parts.find((part) => part.part_id === part_id);
|
|
311
|
+
const provider_metadata = to_session_provider_metadata(chunk.providerMetadata);
|
|
312
|
+
await this.upsert_part({
|
|
313
|
+
part_id,
|
|
314
|
+
sequence: current_part?.sequence || this.next_part_sequence(),
|
|
315
|
+
type: "source",
|
|
316
|
+
source_type: "url",
|
|
317
|
+
source_id: chunk.sourceId,
|
|
318
|
+
url: chunk.url,
|
|
319
|
+
...(chunk.title !== undefined ? { title: chunk.title } : {}),
|
|
320
|
+
...(provider_metadata !== undefined
|
|
321
|
+
? { provider_metadata }
|
|
322
|
+
: {}),
|
|
323
|
+
});
|
|
240
324
|
return;
|
|
325
|
+
}
|
|
326
|
+
case "source-document": {
|
|
327
|
+
const part_id = `source:${chunk.sourceId}`;
|
|
328
|
+
const current_part = current.parts.find((part) => part.part_id === part_id);
|
|
329
|
+
const provider_metadata = to_session_provider_metadata(chunk.providerMetadata);
|
|
330
|
+
await this.upsert_part({
|
|
331
|
+
part_id,
|
|
332
|
+
sequence: current_part?.sequence || this.next_part_sequence(),
|
|
333
|
+
type: "source",
|
|
334
|
+
source_type: "document",
|
|
335
|
+
source_id: chunk.sourceId,
|
|
336
|
+
media_type: chunk.mediaType,
|
|
337
|
+
title: chunk.title,
|
|
338
|
+
...(chunk.filename !== undefined ? { filename: chunk.filename } : {}),
|
|
339
|
+
...(provider_metadata !== undefined
|
|
340
|
+
? { provider_metadata }
|
|
341
|
+
: {}),
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
case "start-step":
|
|
346
|
+
await this.upsert_part({
|
|
347
|
+
part_id: `step:${generateId()}`,
|
|
348
|
+
sequence: this.next_part_sequence(),
|
|
349
|
+
type: "step-start",
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
default: {
|
|
353
|
+
if (chunk.type.startsWith("data-")) {
|
|
354
|
+
const data_chunk = chunk as unknown as Record<string, unknown>;
|
|
355
|
+
if (data_chunk.transient === true) return;
|
|
356
|
+
const data_id = typeof data_chunk.id === "string"
|
|
357
|
+
? data_chunk.id
|
|
358
|
+
: undefined;
|
|
359
|
+
const part_id = `data:${data_id ?? generateId()}`;
|
|
360
|
+
const current_part = current.parts.find((part) => part.part_id === part_id);
|
|
361
|
+
await this.upsert_part({
|
|
362
|
+
part_id,
|
|
363
|
+
sequence: current_part?.sequence || this.next_part_sequence(),
|
|
364
|
+
type: "data",
|
|
365
|
+
data_type: chunk.type,
|
|
366
|
+
data: to_session_json_value(data_chunk.data),
|
|
367
|
+
...(data_id !== undefined ? { data_id } : {}),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
241
372
|
}
|
|
242
373
|
}
|
|
243
374
|
|
|
@@ -285,7 +416,10 @@ export class SessionAssistantMessageWriter {
|
|
|
285
416
|
|
|
286
417
|
/** 把最终结果中的文件补入当前 Assistant,并对流式已写入文件去重。 */
|
|
287
418
|
async append_file_part(
|
|
288
|
-
input: Pick<
|
|
419
|
+
input: Pick<
|
|
420
|
+
SessionAssistantFilePart,
|
|
421
|
+
"filename" | "media_type" | "provider_metadata" | "url"
|
|
422
|
+
>,
|
|
289
423
|
): Promise<void> {
|
|
290
424
|
const filename = String(input.filename || "").trim();
|
|
291
425
|
const current = this.current_message();
|
|
@@ -296,8 +430,17 @@ export class SessionAssistantMessageWriter {
|
|
|
296
430
|
part.media_type === input.media_type,
|
|
297
431
|
);
|
|
298
432
|
if (existing?.type === "file") {
|
|
299
|
-
if (
|
|
300
|
-
|
|
433
|
+
if (
|
|
434
|
+
(filename && String(existing.filename || "").trim() !== filename) ||
|
|
435
|
+
input.provider_metadata !== undefined
|
|
436
|
+
) {
|
|
437
|
+
await this.upsert_part({
|
|
438
|
+
...existing,
|
|
439
|
+
...(filename ? { filename } : {}),
|
|
440
|
+
...(input.provider_metadata !== undefined
|
|
441
|
+
? { provider_metadata: input.provider_metadata }
|
|
442
|
+
: {}),
|
|
443
|
+
});
|
|
301
444
|
}
|
|
302
445
|
return;
|
|
303
446
|
}
|
|
@@ -308,6 +451,9 @@ export class SessionAssistantMessageWriter {
|
|
|
308
451
|
media_type: input.media_type,
|
|
309
452
|
url: input.url,
|
|
310
453
|
...(filename ? { filename } : {}),
|
|
454
|
+
...(input.provider_metadata !== undefined
|
|
455
|
+
? { provider_metadata: input.provider_metadata }
|
|
456
|
+
: {}),
|
|
311
457
|
});
|
|
312
458
|
}
|
|
313
459
|
|
|
@@ -392,6 +538,7 @@ export class SessionAssistantMessageWriter {
|
|
|
392
538
|
private async ensure_text_part(
|
|
393
539
|
part_id: string,
|
|
394
540
|
type: "text" | "reasoning",
|
|
541
|
+
provider_metadata?: SessionAssistantTextPart["provider_metadata"],
|
|
395
542
|
): Promise<void> {
|
|
396
543
|
const existing = this.current_message().parts.find(
|
|
397
544
|
(part) => part.part_id === part_id,
|
|
@@ -400,10 +547,16 @@ export class SessionAssistantMessageWriter {
|
|
|
400
547
|
if (existing.type !== type) {
|
|
401
548
|
throw new Error(`Assistant Part type changed: ${part_id}`);
|
|
402
549
|
}
|
|
550
|
+
if (
|
|
551
|
+
(existing.type === "text" || existing.type === "reasoning") &&
|
|
552
|
+
provider_metadata !== undefined
|
|
553
|
+
) {
|
|
554
|
+
await this.upsert_part({ ...existing, provider_metadata });
|
|
555
|
+
}
|
|
403
556
|
return;
|
|
404
557
|
}
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
558
|
+
const pending = this.pending_text_parts.get(part_id);
|
|
559
|
+
if (pending && pending.type !== type) {
|
|
407
560
|
throw new Error(`Assistant pending Part type changed: ${part_id}`);
|
|
408
561
|
}
|
|
409
562
|
await this.upsert_part({
|
|
@@ -412,6 +565,11 @@ export class SessionAssistantMessageWriter {
|
|
|
412
565
|
type,
|
|
413
566
|
text: "",
|
|
414
567
|
state: "streaming",
|
|
568
|
+
...(provider_metadata !== undefined
|
|
569
|
+
? { provider_metadata }
|
|
570
|
+
: pending?.provider_metadata !== undefined
|
|
571
|
+
? { provider_metadata: pending.provider_metadata }
|
|
572
|
+
: {}),
|
|
415
573
|
});
|
|
416
574
|
this.pending_text_parts.delete(part_id);
|
|
417
575
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ProviderMetadata } from "ai";
|
|
9
|
-
import type { JsonValue } from "@/types/common/Json.js";
|
|
9
|
+
import type { JsonObject, JsonValue } from "@/types/common/Json.js";
|
|
10
10
|
|
|
11
11
|
/** 把任意运行时输入转换为可持久化的 JSON 值。 */
|
|
12
12
|
export function to_session_json_value(input: unknown): JsonValue {
|
|
@@ -25,6 +25,17 @@ export function to_session_json_value(input: unknown): JsonValue {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** 把任意运行时输入转换为可持久化 JSON 对象,非对象输入返回 undefined。 */
|
|
29
|
+
export function to_session_json_object(input: unknown): JsonObject | undefined {
|
|
30
|
+
if (!is_plain_object(input)) return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const value = JSON.parse(JSON.stringify(input)) as unknown;
|
|
33
|
+
return is_plain_object(value) ? value as JsonObject : undefined;
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
/**
|
|
29
40
|
* 把 AI SDK Provider metadata 规整为可持久化快照。
|
|
30
41
|
*
|