@downcity/agent 1.1.62 → 1.1.63
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/model/CityModelAdapter.d.ts.map +1 -1
- package/bin/model/CityModelAdapter.js +129 -13
- package/bin/model/CityModelAdapter.js.map +1 -1
- package/package.json +2 -2
- package/scripts/city-model-tool-loop.test.mjs +181 -0
- package/src/model/CityModelAdapter.ts +184 -15
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -34,6 +34,24 @@ type ProviderPromptMessage = {
|
|
|
34
34
|
|
|
35
35
|
type ProviderStreamController = ReadableStreamDefaultController<Record<string, unknown>>;
|
|
36
36
|
type ProviderContentPart = Record<string, unknown>;
|
|
37
|
+
type ProviderPromptRole = "system" | "user" | "assistant" | "tool";
|
|
38
|
+
|
|
39
|
+
type ProviderToolResultOutput = {
|
|
40
|
+
/**
|
|
41
|
+
* tool result 输出类型。
|
|
42
|
+
*/
|
|
43
|
+
type?: unknown;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* tool result 输出值。
|
|
47
|
+
*/
|
|
48
|
+
value?: unknown;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* tool 拒绝原因。
|
|
52
|
+
*/
|
|
53
|
+
reason?: unknown;
|
|
54
|
+
};
|
|
37
55
|
|
|
38
56
|
function normalizeFinishReason(input: unknown): {
|
|
39
57
|
unified: "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other";
|
|
@@ -72,12 +90,72 @@ function fileUrlFromProviderPart(part: Record<string, unknown>): string {
|
|
|
72
90
|
return "";
|
|
73
91
|
}
|
|
74
92
|
|
|
75
|
-
function
|
|
93
|
+
function resolveProviderPromptRole(input: unknown): ProviderPromptRole {
|
|
94
|
+
return input === "system" || input === "user" || input === "assistant" || input === "tool"
|
|
95
|
+
? input
|
|
96
|
+
: "user";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeProviderToolResultOutput(
|
|
100
|
+
output: ProviderToolResultOutput,
|
|
101
|
+
): {
|
|
102
|
+
/**
|
|
103
|
+
* 归一后的 tool part 状态。
|
|
104
|
+
*/
|
|
105
|
+
state: "output-available" | "output-error";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* tool 成功输出。
|
|
109
|
+
*/
|
|
110
|
+
output?: unknown;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* tool 错误文本。
|
|
114
|
+
*/
|
|
115
|
+
errorText?: string;
|
|
116
|
+
} {
|
|
117
|
+
const outputType = typeof output.type === "string" ? output.type : "";
|
|
118
|
+
if (outputType === "json" || outputType === "text" || outputType === "content") {
|
|
119
|
+
return {
|
|
120
|
+
state: "output-available",
|
|
121
|
+
output: "value" in output ? output.value : null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (outputType === "error-json") {
|
|
125
|
+
return {
|
|
126
|
+
state: "output-error",
|
|
127
|
+
errorText: stringifyToolInput("value" in output ? output.value : null),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (outputType === "error-text") {
|
|
131
|
+
return {
|
|
132
|
+
state: "output-error",
|
|
133
|
+
errorText: String(output.value ?? ""),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (outputType === "execution-denied") {
|
|
137
|
+
return {
|
|
138
|
+
state: "output-error",
|
|
139
|
+
errorText: String(output.reason ?? "tool execution denied"),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
state: "output-available",
|
|
144
|
+
output,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function providerContentToUiParts(
|
|
149
|
+
content: unknown,
|
|
150
|
+
existingParts?: UIMessage["parts"],
|
|
151
|
+
): UIMessage["parts"] {
|
|
76
152
|
if (!Array.isArray(content)) {
|
|
77
153
|
return [{ type: "text", text: textFromProviderContent(content) }];
|
|
78
154
|
}
|
|
79
155
|
|
|
80
|
-
const parts: UIMessage["parts"] =
|
|
156
|
+
const parts: UIMessage["parts"] = Array.isArray(existingParts)
|
|
157
|
+
? [...existingParts]
|
|
158
|
+
: [];
|
|
81
159
|
for (const part of content) {
|
|
82
160
|
if (!part || typeof part !== "object") continue;
|
|
83
161
|
const record = part as Record<string, unknown>;
|
|
@@ -109,6 +187,55 @@ function providerContentToUiParts(content: unknown): UIMessage["parts"] {
|
|
|
109
187
|
input: record.input,
|
|
110
188
|
providerExecuted: Boolean(record.providerExecuted),
|
|
111
189
|
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (record.type === "tool-result") {
|
|
193
|
+
const toolCallId = String(record.toolCallId ?? "");
|
|
194
|
+
const toolName = String(record.toolName ?? "");
|
|
195
|
+
const normalizedOutput = normalizeProviderToolResultOutput(
|
|
196
|
+
(record.output as ProviderToolResultOutput | undefined) ?? {},
|
|
197
|
+
);
|
|
198
|
+
const existingPart = parts.find((item) => {
|
|
199
|
+
if (!item || typeof item !== "object") return false;
|
|
200
|
+
const toolPart = item as { toolCallId?: unknown };
|
|
201
|
+
return String(toolPart.toolCallId ?? "") === toolCallId;
|
|
202
|
+
}) as
|
|
203
|
+
| ({
|
|
204
|
+
toolCallId?: unknown;
|
|
205
|
+
toolName?: unknown;
|
|
206
|
+
input?: unknown;
|
|
207
|
+
providerExecuted?: unknown;
|
|
208
|
+
} & Record<string, unknown>)
|
|
209
|
+
| undefined;
|
|
210
|
+
const baseInput = existingPart?.input ?? null;
|
|
211
|
+
const baseToolName = String(existingPart?.toolName ?? toolName);
|
|
212
|
+
const nextPart = normalizedOutput.state === "output-available"
|
|
213
|
+
? {
|
|
214
|
+
type: "dynamic-tool" as const,
|
|
215
|
+
toolName: baseToolName,
|
|
216
|
+
toolCallId,
|
|
217
|
+
state: "output-available" as const,
|
|
218
|
+
input: baseInput,
|
|
219
|
+
output: normalizedOutput.output,
|
|
220
|
+
providerExecuted: false,
|
|
221
|
+
}
|
|
222
|
+
: {
|
|
223
|
+
type: "dynamic-tool" as const,
|
|
224
|
+
toolName: baseToolName,
|
|
225
|
+
toolCallId,
|
|
226
|
+
state: "output-error" as const,
|
|
227
|
+
input: baseInput,
|
|
228
|
+
errorText: normalizedOutput.errorText ?? "tool_error",
|
|
229
|
+
providerExecuted: false,
|
|
230
|
+
};
|
|
231
|
+
if (existingPart) {
|
|
232
|
+
const index = parts.indexOf(existingPart as never);
|
|
233
|
+
if (index >= 0) {
|
|
234
|
+
parts[index] = nextPart;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
parts.push(nextPart);
|
|
112
239
|
}
|
|
113
240
|
}
|
|
114
241
|
return parts;
|
|
@@ -116,19 +243,36 @@ function providerContentToUiParts(content: unknown): UIMessage["parts"] {
|
|
|
116
243
|
|
|
117
244
|
function providerPromptToMessages(prompt: unknown): UIMessage[] {
|
|
118
245
|
if (!Array.isArray(prompt)) return [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
246
|
+
const messages: UIMessage[] = [];
|
|
247
|
+
for (const [index, message] of prompt.entries()) {
|
|
248
|
+
if (!message || typeof message !== "object") continue;
|
|
249
|
+
const item = message as ProviderPromptMessage;
|
|
250
|
+
const role = resolveProviderPromptRole(item.role);
|
|
251
|
+
if (role === "tool") {
|
|
252
|
+
const lastAssistantMessage = [...messages]
|
|
253
|
+
.reverse()
|
|
254
|
+
.find((candidate) => candidate.role === "assistant");
|
|
255
|
+
if (lastAssistantMessage) {
|
|
256
|
+
lastAssistantMessage.parts = providerContentToUiParts(
|
|
257
|
+
item.content,
|
|
258
|
+
lastAssistantMessage.parts,
|
|
259
|
+
);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
messages.push({
|
|
126
263
|
id: `city-model-message-${String(index)}`,
|
|
127
|
-
role,
|
|
128
|
-
parts,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
|
|
264
|
+
role: "assistant",
|
|
265
|
+
parts: providerContentToUiParts(item.content),
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
messages.push({
|
|
270
|
+
id: `city-model-message-${String(index)}`,
|
|
271
|
+
role,
|
|
272
|
+
parts: providerContentToUiParts(item.content),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return messages;
|
|
132
276
|
}
|
|
133
277
|
|
|
134
278
|
function providerOptionsToInput(options: Record<string, unknown>): CityModelInvokeInput {
|
|
@@ -161,15 +305,40 @@ function uiMessageToProviderContent(message: UIMessage): ProviderContentPart[] {
|
|
|
161
305
|
toolCallId?: unknown;
|
|
162
306
|
toolName?: unknown;
|
|
163
307
|
input?: unknown;
|
|
308
|
+
output?: unknown;
|
|
309
|
+
errorText?: unknown;
|
|
310
|
+
state?: unknown;
|
|
164
311
|
providerExecuted?: unknown;
|
|
165
312
|
};
|
|
166
|
-
|
|
313
|
+
const content: ProviderContentPart[] = [{
|
|
167
314
|
type: "tool-call",
|
|
168
315
|
toolCallId: String(toolPart.toolCallId ?? ""),
|
|
169
316
|
toolName: String(toolPart.toolName ?? ""),
|
|
170
317
|
input: stringifyToolInput(toolPart.input),
|
|
171
318
|
providerExecuted: Boolean(toolPart.providerExecuted),
|
|
172
319
|
}];
|
|
320
|
+
if (toolPart.state === "output-available") {
|
|
321
|
+
content.push({
|
|
322
|
+
type: "tool-result",
|
|
323
|
+
toolCallId: String(toolPart.toolCallId ?? ""),
|
|
324
|
+
toolName: String(toolPart.toolName ?? ""),
|
|
325
|
+
output: {
|
|
326
|
+
type: "json",
|
|
327
|
+
value: toolPart.output ?? null,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
} else if (toolPart.state === "output-error") {
|
|
331
|
+
content.push({
|
|
332
|
+
type: "tool-result",
|
|
333
|
+
toolCallId: String(toolPart.toolCallId ?? ""),
|
|
334
|
+
toolName: String(toolPart.toolName ?? ""),
|
|
335
|
+
output: {
|
|
336
|
+
type: "error-text",
|
|
337
|
+
value: String(toolPart.errorText ?? "tool_error"),
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return content;
|
|
173
342
|
}
|
|
174
343
|
return [];
|
|
175
344
|
});
|