@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.
@@ -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 providerContentToUiParts(content: unknown): UIMessage["parts"] {
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
- return prompt
120
- .map((message, index): UIMessage | null => {
121
- if (!message || typeof message !== "object") return null;
122
- const item = message as ProviderPromptMessage;
123
- const role = item.role === "system" || item.role === "assistant" ? item.role : "user";
124
- const parts = providerContentToUiParts(item.content);
125
- return {
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
- .filter((message): message is UIMessage => Boolean(message));
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
- return [{
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
  });