veryfront 0.1.438 → 0.1.440
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/esm/deno.js +1 -1
- package/esm/extensions/ext-anthropic/src/anthropic-provider.d.ts.map +1 -1
- package/esm/extensions/ext-anthropic/src/anthropic-provider.js +23 -5
- package/esm/extensions/ext-google/src/google-provider.d.ts.map +1 -1
- package/esm/extensions/ext-google/src/google-provider.js +22 -2
- package/esm/extensions/ext-openai/src/openai-provider.d.ts.map +1 -1
- package/esm/extensions/ext-openai/src/openai-provider.js +23 -2
- package/esm/src/agent/hosted-durable-chat-run-start.d.ts +46 -0
- package/esm/src/agent/hosted-durable-chat-run-start.d.ts.map +1 -0
- package/esm/src/agent/hosted-durable-chat-run-start.js +148 -0
- package/esm/src/agent/index.d.ts +2 -0
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -0
- package/esm/src/agent/runtime/text-generation-runtime-message-converter.d.ts.map +1 -1
- package/esm/src/agent/runtime/text-generation-runtime-message-converter.js +38 -2
- package/esm/src/agent/runtime/text-generation-runtime-message-types.d.ts +7 -1
- package/esm/src/agent/runtime/text-generation-runtime-message-types.d.ts.map +1 -1
- package/esm/src/agent/runtime-message-file-url-refresh.d.ts.map +1 -1
- package/esm/src/agent/runtime-message-file-url-refresh.js +64 -10
- package/esm/src/chat/conversation.d.ts.map +1 -1
- package/esm/src/chat/conversation.js +8 -5
- package/esm/src/provider/runtime-loader.d.ts +14 -1
- package/esm/src/provider/runtime-loader.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader.js +19 -1
- package/esm/src/runtime/runtime-bridge.d.ts.map +1 -1
- package/esm/src/runtime/runtime-bridge.js +3 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/extensions/ext-anthropic/src/anthropic-provider.ts +28 -5
- package/src/extensions/ext-google/src/google-provider.ts +27 -2
- package/src/extensions/ext-openai/src/openai-provider.ts +29 -2
- package/src/src/agent/hosted-durable-chat-run-start.ts +246 -0
- package/src/src/agent/index.ts +12 -0
- package/src/src/agent/runtime/text-generation-runtime-message-converter.ts +41 -2
- package/src/src/agent/runtime/text-generation-runtime-message-types.ts +8 -1
- package/src/src/agent/runtime-message-file-url-refresh.ts +78 -12
- package/src/src/chat/conversation.ts +15 -9
- package/src/src/provider/runtime-loader.ts +46 -3
- package/src/src/runtime/runtime-bridge.ts +10 -2
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { parseProviderError } from "../chat/provider-errors.js";
|
|
2
|
+
import {
|
|
3
|
+
AgUiDetachedStartAcceptedSchema,
|
|
4
|
+
buildDetachedAgUiStartRequest,
|
|
5
|
+
executeAgUiDetachedStart,
|
|
6
|
+
} from "./ag-ui-detached-start.js";
|
|
7
|
+
import type { AgUiResumeValue } from "./ag-ui-tool-shared.js";
|
|
8
|
+
import type { DetachedRunTracker } from "./detached-run-tracker.js";
|
|
9
|
+
import type { ParsedHostedChatRequest } from "./hosted-chat-request-parser.js";
|
|
10
|
+
|
|
11
|
+
export type HostedDurableRunSetupErrorStatusCode = 400 | 402 | 413 | 429 | 500 | 503;
|
|
12
|
+
|
|
13
|
+
export type HostedDurableRunAccepted = {
|
|
14
|
+
accepted: boolean;
|
|
15
|
+
duplicate: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type HostedDurableRunAuthErrorResponse = {
|
|
19
|
+
errorCode: string;
|
|
20
|
+
statusCode: number;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type HostedDurableRunLogger = {
|
|
25
|
+
error(message: string, metadata?: Record<string, unknown>): void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type HostedDurableRunStartExecutionInput<TExecution> = {
|
|
29
|
+
execution: TExecution;
|
|
30
|
+
abortSignal: AbortSignal;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type HostedDurableRunStartCleanupInput<TExecution> = {
|
|
34
|
+
execution: TExecution;
|
|
35
|
+
runId: string;
|
|
36
|
+
conversationId: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ExecuteHostedDurableChatRunInput<TExecution> = {
|
|
40
|
+
req: ParsedHostedChatRequest;
|
|
41
|
+
rawRequest: Request;
|
|
42
|
+
requestOrCtx?: unknown;
|
|
43
|
+
tracker: DetachedRunTracker<AgUiResumeValue>;
|
|
44
|
+
prepareExecution: (req: ParsedHostedChatRequest) => Promise<TExecution>;
|
|
45
|
+
startDetachedExecution: (
|
|
46
|
+
input: HostedDurableRunStartExecutionInput<TExecution>,
|
|
47
|
+
) => Promise<void>;
|
|
48
|
+
cleanupExecution?: (input: HostedDurableRunStartCleanupInput<TExecution>) => Promise<void>;
|
|
49
|
+
resolveAuthError?: (error: unknown) => HostedDurableRunAuthErrorResponse | null | undefined;
|
|
50
|
+
logger?: HostedDurableRunLogger;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function readBooleanProperty(input: object | null, propertyName: string): boolean {
|
|
54
|
+
if (!input) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Object.getOwnPropertyDescriptor(input, propertyName)?.value === true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isDurableRunSetupErrorStatusCode(
|
|
62
|
+
status: number | undefined,
|
|
63
|
+
): status is HostedDurableRunSetupErrorStatusCode {
|
|
64
|
+
return status === 400 || status === 402 || status === 413 || status === 429 ||
|
|
65
|
+
status === 500 || status === 503;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function fallbackDurableRunSetupErrorStatusCode(
|
|
69
|
+
code: string,
|
|
70
|
+
): HostedDurableRunSetupErrorStatusCode {
|
|
71
|
+
if (code === "OVERLOADED_ERROR") return 503;
|
|
72
|
+
if (code === "CONTEXT_LENGTH_EXCEEDED") return 413;
|
|
73
|
+
|
|
74
|
+
return 500;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveHostedDurableRunSetupErrorResponse(input: {
|
|
78
|
+
code: string;
|
|
79
|
+
status?: number;
|
|
80
|
+
originalError: unknown;
|
|
81
|
+
}): {
|
|
82
|
+
errorCode: string;
|
|
83
|
+
statusCode: HostedDurableRunSetupErrorStatusCode;
|
|
84
|
+
} {
|
|
85
|
+
if (
|
|
86
|
+
input.originalError instanceof Error &&
|
|
87
|
+
input.originalError.message === "DURABLE_CHAT_ROOT_REQUIRES_CONVERSATION"
|
|
88
|
+
) {
|
|
89
|
+
return {
|
|
90
|
+
errorCode: "DURABLE_CHAT_ROOT_REQUIRES_CONVERSATION",
|
|
91
|
+
statusCode: 400,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
errorCode: input.code,
|
|
97
|
+
statusCode: isDurableRunSetupErrorStatusCode(input.status)
|
|
98
|
+
? input.status
|
|
99
|
+
: fallbackDurableRunSetupErrorStatusCode(input.code),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function parseAcceptedDetachedStartResponse(
|
|
104
|
+
response: Response,
|
|
105
|
+
): Promise<HostedDurableRunAccepted> {
|
|
106
|
+
if (response.status !== 202) {
|
|
107
|
+
return { accepted: false, duplicate: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const payload = await response.json().catch((): null => null);
|
|
111
|
+
const parsed = AgUiDetachedStartAcceptedSchema.safeParse(payload);
|
|
112
|
+
if (parsed.success) {
|
|
113
|
+
return {
|
|
114
|
+
accepted: parsed.data.accepted,
|
|
115
|
+
duplicate: parsed.data.duplicate,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const payloadObject = typeof payload === "object" ? payload : null;
|
|
120
|
+
return {
|
|
121
|
+
accepted: readBooleanProperty(payloadObject, "accepted"),
|
|
122
|
+
duplicate: readBooleanProperty(payloadObject, "duplicate"),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function executeHostedDurableChatRunStart<TExecution>(
|
|
127
|
+
input: ExecuteHostedDurableChatRunInput<TExecution>,
|
|
128
|
+
): Promise<Response | HostedDurableRunAccepted> {
|
|
129
|
+
const { durableRootRun, conversationId } = input.req;
|
|
130
|
+
if (!durableRootRun || !conversationId) {
|
|
131
|
+
throw new Error("DURABLE_CHAT_ROOT_REQUIRES_CONVERSATION");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const execution = await input.prepareExecution(input.req);
|
|
135
|
+
const detachedStartRequest = buildDetachedAgUiStartRequest({
|
|
136
|
+
runId: durableRootRun.runId,
|
|
137
|
+
threadId: conversationId,
|
|
138
|
+
messages: input.req.messages,
|
|
139
|
+
model: input.req.model,
|
|
140
|
+
forwardedProps: input.req.forwardedProps,
|
|
141
|
+
});
|
|
142
|
+
const detachedStartResponse = await executeAgUiDetachedStart(
|
|
143
|
+
{
|
|
144
|
+
sessionManager: input.tracker.sessionManager,
|
|
145
|
+
startDetachedExecution: async ({ abortSignal }) => {
|
|
146
|
+
const detachedExecution = input.startDetachedExecution({
|
|
147
|
+
execution,
|
|
148
|
+
abortSignal,
|
|
149
|
+
});
|
|
150
|
+
input.tracker.registerExecution(durableRootRun.runId, detachedExecution);
|
|
151
|
+
await detachedExecution;
|
|
152
|
+
},
|
|
153
|
+
onDuplicate: async () => {
|
|
154
|
+
await input.cleanupExecution?.({
|
|
155
|
+
execution,
|
|
156
|
+
runId: durableRootRun.runId,
|
|
157
|
+
conversationId,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
onAccepted: async () => {
|
|
161
|
+
input.tracker.trackRun(durableRootRun.runId);
|
|
162
|
+
},
|
|
163
|
+
onError: async ({ error }) => {
|
|
164
|
+
input.tracker.untrackRun(durableRootRun.runId);
|
|
165
|
+
input.logger?.error("Detached durable run execution failed", {
|
|
166
|
+
runId: durableRootRun.runId,
|
|
167
|
+
conversationId,
|
|
168
|
+
error: error instanceof Error ? error.message : String(error),
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
request: detachedStartRequest,
|
|
174
|
+
rawRequest: input.rawRequest,
|
|
175
|
+
requestOrCtx: input.requestOrCtx ?? input.rawRequest,
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (detachedStartResponse.status !== 202) {
|
|
180
|
+
return detachedStartResponse;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return await parseAcceptedDetachedStartResponse(detachedStartResponse);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function executeHostedDurableChatRun<TExecution>(
|
|
187
|
+
input: ExecuteHostedDurableChatRunInput<TExecution>,
|
|
188
|
+
): Promise<Response> {
|
|
189
|
+
const { durableRootRun, conversationId, projectId, userId } = input.req;
|
|
190
|
+
if (!durableRootRun || !conversationId) {
|
|
191
|
+
return Response.json(
|
|
192
|
+
{ errorCode: "DURABLE_CHAT_ROOT_REQUIRES_CONVERSATION" },
|
|
193
|
+
{ status: 400 },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const existingRunStatus = input.tracker.sessionManager.getRunStatus(durableRootRun.runId);
|
|
198
|
+
if (existingRunStatus === "running" || existingRunStatus === "waiting") {
|
|
199
|
+
return Response.json({ accepted: true, duplicate: true }, { status: 202 });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const startResult = await executeHostedDurableChatRunStart(input);
|
|
204
|
+
|
|
205
|
+
if (startResult instanceof Response) {
|
|
206
|
+
return startResult;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return Response.json(startResult, { status: 202 });
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const authError = input.resolveAuthError?.(error);
|
|
212
|
+
if (authError) {
|
|
213
|
+
input.logger?.error("Durable chat auth error from API", {
|
|
214
|
+
errorCode: authError.errorCode,
|
|
215
|
+
statusCode: authError.statusCode,
|
|
216
|
+
projectId,
|
|
217
|
+
userId,
|
|
218
|
+
runId: durableRootRun.runId,
|
|
219
|
+
...authError.metadata,
|
|
220
|
+
});
|
|
221
|
+
return Response.json(
|
|
222
|
+
{ errorCode: authError.errorCode },
|
|
223
|
+
{ status: authError.statusCode },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { code, status } = parseProviderError(error);
|
|
228
|
+
const response = resolveHostedDurableRunSetupErrorResponse({
|
|
229
|
+
code,
|
|
230
|
+
status,
|
|
231
|
+
originalError: error,
|
|
232
|
+
});
|
|
233
|
+
input.logger?.error("Durable chat execute failed during setup", {
|
|
234
|
+
errorCode: code,
|
|
235
|
+
originalError: error instanceof Error ? error.message : String(error),
|
|
236
|
+
projectId,
|
|
237
|
+
userId,
|
|
238
|
+
runId: durableRootRun.runId,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return Response.json(
|
|
242
|
+
{ errorCode: response.errorCode },
|
|
243
|
+
{ status: response.statusCode },
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
package/src/src/agent/index.ts
CHANGED
|
@@ -282,6 +282,17 @@ export type {
|
|
|
282
282
|
HostedChatRuntimeToUiMessageStreamOptions,
|
|
283
283
|
} from "./hosted-chat-runtime-contract.js";
|
|
284
284
|
|
|
285
|
+
export {
|
|
286
|
+
executeHostedDurableChatRun,
|
|
287
|
+
type ExecuteHostedDurableChatRunInput,
|
|
288
|
+
type HostedDurableRunAccepted,
|
|
289
|
+
type HostedDurableRunAuthErrorResponse,
|
|
290
|
+
type HostedDurableRunLogger,
|
|
291
|
+
type HostedDurableRunSetupErrorStatusCode,
|
|
292
|
+
type HostedDurableRunStartCleanupInput,
|
|
293
|
+
type HostedDurableRunStartExecutionInput,
|
|
294
|
+
resolveHostedDurableRunSetupErrorResponse,
|
|
295
|
+
} from "./hosted-durable-chat-run-start.js";
|
|
285
296
|
export {
|
|
286
297
|
buildParsedHostedChatRequest,
|
|
287
298
|
type HostedChatProjectAccessError,
|
|
@@ -1082,6 +1093,7 @@ export {
|
|
|
1082
1093
|
executeAgUiDetachedStart,
|
|
1083
1094
|
type ExecuteAgUiDetachedStartInput,
|
|
1084
1095
|
} from "./ag-ui-detached-start.js";
|
|
1096
|
+
export type { AgUiResumeValue } from "./ag-ui-tool-shared.js";
|
|
1085
1097
|
export {
|
|
1086
1098
|
createDetachedRunTracker,
|
|
1087
1099
|
type DetachedRunDrainResult,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
11
|
TextGenerationRuntimeAssistantMessage,
|
|
12
|
+
TextGenerationRuntimeFilePart,
|
|
12
13
|
TextGenerationRuntimeMessage,
|
|
13
14
|
TextGenerationRuntimeTextPart,
|
|
14
15
|
TextGenerationRuntimeToolCallPart,
|
|
@@ -75,6 +76,26 @@ function getUserTextWithAttachmentContext(parts: Message["parts"]): string {
|
|
|
75
76
|
: appendReadableAttachmentContext(text, buildAttachmentContextFromParts(parts));
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
function getUserFileParts(parts: Message["parts"]): TextGenerationRuntimeFilePart[] {
|
|
80
|
+
return parts.flatMap((part) => {
|
|
81
|
+
const type = getStringPartField(part, "type");
|
|
82
|
+
if (type !== "file" && type !== "image") return [];
|
|
83
|
+
|
|
84
|
+
const mediaType = getStringPartField(part, "mediaType");
|
|
85
|
+
const url = getStringPartField(part, "url");
|
|
86
|
+
if (!mediaType || !url || url.startsWith("data:")) return [];
|
|
87
|
+
|
|
88
|
+
return [{
|
|
89
|
+
type,
|
|
90
|
+
mediaType,
|
|
91
|
+
url,
|
|
92
|
+
...(getStringPartField(part, "filename")
|
|
93
|
+
? { filename: getStringPartField(part, "filename") }
|
|
94
|
+
: {}),
|
|
95
|
+
}];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
78
99
|
/**
|
|
79
100
|
* Convert a veryfront Message to the current text-generation runtime message format.
|
|
80
101
|
*/
|
|
@@ -86,8 +107,26 @@ export function convertToTextGenerationRuntimeMessage(msg: Message): TextGenerat
|
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
case "user": {
|
|
89
|
-
const
|
|
90
|
-
|
|
110
|
+
const fileParts = getUserFileParts(msg.parts);
|
|
111
|
+
if (fileParts.length === 0) {
|
|
112
|
+
const text = getUserTextWithAttachmentContext(msg.parts);
|
|
113
|
+
return { role: "user", content: text };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const text = getTextFromParts(msg.parts);
|
|
117
|
+
const attachmentContext = text.includes("<uploaded_files>")
|
|
118
|
+
? ""
|
|
119
|
+
: buildAttachmentContextFromParts(msg.parts);
|
|
120
|
+
return {
|
|
121
|
+
role: "user",
|
|
122
|
+
content: [
|
|
123
|
+
...(text.length > 0 ? [{ type: "text" as const, text }] : []),
|
|
124
|
+
...fileParts,
|
|
125
|
+
...(attachmentContext.length > 0
|
|
126
|
+
? [{ type: "text" as const, text: attachmentContext.trimStart() }]
|
|
127
|
+
: []),
|
|
128
|
+
],
|
|
129
|
+
};
|
|
91
130
|
}
|
|
92
131
|
|
|
93
132
|
case "assistant": {
|
|
@@ -11,6 +11,13 @@ export interface TextGenerationRuntimeTextPart {
|
|
|
11
11
|
text: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface TextGenerationRuntimeFilePart {
|
|
15
|
+
type: "file" | "image";
|
|
16
|
+
mediaType: string;
|
|
17
|
+
url: string;
|
|
18
|
+
filename?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
export interface TextGenerationRuntimeToolCallPart {
|
|
15
22
|
type: "tool-call";
|
|
16
23
|
toolCallId: string;
|
|
@@ -35,7 +42,7 @@ export interface TextGenerationRuntimeSystemMessage {
|
|
|
35
42
|
|
|
36
43
|
export interface TextGenerationRuntimeUserMessage {
|
|
37
44
|
role: "user";
|
|
38
|
-
content: string
|
|
45
|
+
content: string | Array<TextGenerationRuntimeTextPart | TextGenerationRuntimeFilePart>;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface TextGenerationRuntimeAssistantMessage {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChatUiMessage, FileUIPartWithUpload } from "../chat/types.js";
|
|
2
2
|
|
|
3
3
|
export type RuntimeFileUrlResolverInput = {
|
|
4
4
|
uploadId: string;
|
|
@@ -18,25 +18,30 @@ export async function resolveRuntimeMessageFileUrls(
|
|
|
18
18
|
|
|
19
19
|
return Promise.all(
|
|
20
20
|
messages.map(async (message) => {
|
|
21
|
-
if (!message.parts.some((part) => part
|
|
21
|
+
if (!message.parts.some((part) => getUploadId(part))) {
|
|
22
22
|
return message;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const parts = await Promise.all(
|
|
26
26
|
message.parts.map(async (part) => {
|
|
27
|
-
|
|
27
|
+
const uploadId = getUploadId(part);
|
|
28
|
+
if (!uploadId) return part;
|
|
28
29
|
|
|
29
|
-
let urlPromise = urlByUploadId.get(
|
|
30
|
+
let urlPromise = urlByUploadId.get(uploadId);
|
|
30
31
|
if (!urlPromise) {
|
|
31
|
-
urlPromise = resolveFileUrl({
|
|
32
|
-
|
|
32
|
+
urlPromise = resolveFileUrl({
|
|
33
|
+
uploadId,
|
|
34
|
+
part: toResolverPart(part, uploadId),
|
|
35
|
+
message,
|
|
36
|
+
});
|
|
37
|
+
urlByUploadId.set(uploadId, urlPromise);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
const signedUrl = await urlPromise;
|
|
36
|
-
if (!signedUrl
|
|
41
|
+
if (!signedUrl) return normalizeUploadedFilePart(part, uploadId);
|
|
37
42
|
|
|
38
43
|
return {
|
|
39
|
-
...part,
|
|
44
|
+
...normalizeUploadedFilePart(part, uploadId),
|
|
40
45
|
url: signedUrl,
|
|
41
46
|
};
|
|
42
47
|
}),
|
|
@@ -47,8 +52,69 @@ export async function resolveRuntimeMessageFileUrls(
|
|
|
47
52
|
);
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
55
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getStringField(part: unknown, key: string): string | undefined {
|
|
60
|
+
if (!isRecord(part)) return undefined;
|
|
61
|
+
|
|
62
|
+
const value = part[key];
|
|
63
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getUploadId(part: unknown): string | undefined {
|
|
67
|
+
if (!isRecord(part) || (part.type !== "file" && part.type !== "image")) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return getStringField(part, "uploadId") ?? getStringField(part, "upload_id");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getMediaType(part: unknown): string | undefined {
|
|
75
|
+
return getStringField(part, "mediaType") ?? getStringField(part, "media_type");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeUploadedFilePart(
|
|
79
|
+
part: ChatUiMessage["parts"][number],
|
|
80
|
+
uploadId: string,
|
|
81
|
+
): ChatUiMessage["parts"][number] {
|
|
82
|
+
if (!isRecord(part)) return part;
|
|
83
|
+
|
|
84
|
+
const partRecord: Record<string, unknown> = part;
|
|
85
|
+
const partType = partRecord.type;
|
|
86
|
+
if (partType !== "file" && partType !== "image") return part;
|
|
87
|
+
|
|
88
|
+
const mediaType = getMediaType(part);
|
|
89
|
+
const url = getStringField(part, "url");
|
|
90
|
+
if (!mediaType || !url) return part;
|
|
91
|
+
|
|
92
|
+
const filename = getStringField(part, "filename");
|
|
93
|
+
const uploadPath = getStringField(part, "uploadPath") ?? getStringField(part, "upload_path");
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
type: partType === "image" ? "image" : "file",
|
|
97
|
+
mediaType,
|
|
98
|
+
url,
|
|
99
|
+
...(filename ? { filename } : {}),
|
|
100
|
+
uploadId,
|
|
101
|
+
...(uploadPath ? { uploadPath } : {}),
|
|
102
|
+
} as ChatUiMessage["parts"][number];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toResolverPart(
|
|
106
|
+
part: ChatUiMessage["parts"][number],
|
|
107
|
+
uploadId: string,
|
|
108
|
+
): FileUIPartWithUpload {
|
|
109
|
+
const normalized = normalizeUploadedFilePart(part, uploadId);
|
|
110
|
+
if (isRecord(normalized) && normalized.type === "file") {
|
|
111
|
+
return normalized as FileUIPartWithUpload;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
type: "file",
|
|
116
|
+
mediaType: getMediaType(part) ?? "application/octet-stream",
|
|
117
|
+
url: getStringField(part, "url") ?? "",
|
|
118
|
+
uploadId,
|
|
119
|
+
};
|
|
54
120
|
}
|
|
@@ -515,7 +515,7 @@ function toJsonValue(value: unknown): JsonValue {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
function getFilePart(part: unknown): {
|
|
518
|
-
type: "file";
|
|
518
|
+
type: "file" | "image";
|
|
519
519
|
mediaType: string;
|
|
520
520
|
data: string;
|
|
521
521
|
url: string;
|
|
@@ -523,22 +523,25 @@ function getFilePart(part: unknown): {
|
|
|
523
523
|
uploadId?: string;
|
|
524
524
|
uploadPath?: string;
|
|
525
525
|
} | null {
|
|
526
|
-
if (!isRecord(part) || part.type !== "file") {
|
|
526
|
+
if (!isRecord(part) || (part.type !== "file" && part.type !== "image")) {
|
|
527
527
|
return null;
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
const mediaType = getNonEmptyStringField(part, "mediaType")
|
|
530
|
+
const mediaType = getNonEmptyStringField(part, "mediaType") ??
|
|
531
|
+
getNonEmptyStringField(part, "media_type");
|
|
531
532
|
const data = getNonEmptyStringField(part, "url");
|
|
532
533
|
if (!mediaType || !data) {
|
|
533
534
|
return null;
|
|
534
535
|
}
|
|
535
536
|
|
|
536
537
|
const filename = getNonEmptyStringField(part, "filename");
|
|
537
|
-
const uploadId = getNonEmptyStringField(part, "uploadId")
|
|
538
|
-
|
|
538
|
+
const uploadId = getNonEmptyStringField(part, "uploadId") ??
|
|
539
|
+
getNonEmptyStringField(part, "upload_id");
|
|
540
|
+
const uploadPath = getNonEmptyStringField(part, "uploadPath") ??
|
|
541
|
+
getNonEmptyStringField(part, "upload_path");
|
|
539
542
|
|
|
540
543
|
return {
|
|
541
|
-
type: "file",
|
|
544
|
+
type: part.type === "image" ? "image" : "file",
|
|
542
545
|
mediaType,
|
|
543
546
|
data,
|
|
544
547
|
url: data,
|
|
@@ -721,10 +724,13 @@ function convertSystemMessage(message: ChatUiMessage): ProviderModelMessage[] {
|
|
|
721
724
|
function convertUserMessage(message: ChatUiMessage): ProviderModelMessage[] {
|
|
722
725
|
const content: Array<
|
|
723
726
|
{ type: "text"; text: string } | {
|
|
724
|
-
type: "file";
|
|
727
|
+
type: "file" | "image";
|
|
725
728
|
mediaType: string;
|
|
726
729
|
data: string;
|
|
730
|
+
url: string;
|
|
727
731
|
filename?: string;
|
|
732
|
+
uploadId?: string;
|
|
733
|
+
uploadPath?: string;
|
|
728
734
|
}
|
|
729
735
|
> = [];
|
|
730
736
|
|
|
@@ -757,7 +763,7 @@ function convertAssistantMessage(message: ChatUiMessage): ProviderModelMessage[]
|
|
|
757
763
|
const assistantContent: Array<
|
|
758
764
|
| { type: "text"; text: string }
|
|
759
765
|
| { type: "reasoning"; text: string }
|
|
760
|
-
| { type: "file"; mediaType: string; data: string; filename?: string }
|
|
766
|
+
| { type: "file" | "image"; mediaType: string; data: string; filename?: string }
|
|
761
767
|
| { type: "tool-call"; toolCallId: string; toolName: string; input: Record<string, unknown> }
|
|
762
768
|
> = [];
|
|
763
769
|
const deferredAssistantContent: typeof assistantContent = [];
|
|
@@ -806,7 +812,7 @@ function convertAssistantMessage(message: ChatUiMessage): ProviderModelMessage[]
|
|
|
806
812
|
part:
|
|
807
813
|
| { type: "text"; text: string }
|
|
808
814
|
| { type: "reasoning"; text: string }
|
|
809
|
-
| { type: "file"; mediaType: string; data: string; filename?: string }
|
|
815
|
+
| { type: "file" | "image"; mediaType: string; data: string; filename?: string }
|
|
810
816
|
| { type: "tool-call"; toolCallId: string; toolName: string; input: Record<string, unknown> },
|
|
811
817
|
) => {
|
|
812
818
|
if (toolResults.length > 0) {
|
|
@@ -34,7 +34,13 @@ export type { RuntimeUsage };
|
|
|
34
34
|
|
|
35
35
|
export type RuntimePromptMessage =
|
|
36
36
|
| { role: "system"; content: string }
|
|
37
|
-
| {
|
|
37
|
+
| {
|
|
38
|
+
role: "user";
|
|
39
|
+
content: Array<
|
|
40
|
+
| { type: "text"; text: string }
|
|
41
|
+
| { type: "image" | "file"; mediaType: string; url: string; filename?: string }
|
|
42
|
+
>;
|
|
43
|
+
}
|
|
38
44
|
| {
|
|
39
45
|
role: "assistant";
|
|
40
46
|
content: Array<
|
|
@@ -261,7 +267,15 @@ type OpenAICompatibleLanguageOptions = {
|
|
|
261
267
|
};
|
|
262
268
|
export type OpenAICompatibleChatMessage =
|
|
263
269
|
| { role: "system"; content: string }
|
|
264
|
-
| {
|
|
270
|
+
| {
|
|
271
|
+
role: "user";
|
|
272
|
+
content:
|
|
273
|
+
| string
|
|
274
|
+
| Array<
|
|
275
|
+
| { type: "text"; text: string }
|
|
276
|
+
| { type: "image_url"; image_url: { url: string } }
|
|
277
|
+
>;
|
|
278
|
+
}
|
|
265
279
|
| {
|
|
266
280
|
role: "assistant";
|
|
267
281
|
content: string | null;
|
|
@@ -279,6 +293,11 @@ export type OpenAICompatibleChatMessage =
|
|
|
279
293
|
tool_call_id: string;
|
|
280
294
|
content: string;
|
|
281
295
|
};
|
|
296
|
+
type RuntimePromptUserContent = Extract<RuntimePromptMessage, { role: "user" }>["content"];
|
|
297
|
+
type OpenAICompatibleUserContent = Extract<
|
|
298
|
+
OpenAICompatibleChatMessage,
|
|
299
|
+
{ role: "user" }
|
|
300
|
+
>["content"];
|
|
282
301
|
export type OpenAICompatibleChatRequest = {
|
|
283
302
|
model: string;
|
|
284
303
|
messages: OpenAICompatibleChatMessage[];
|
|
@@ -359,6 +378,30 @@ export function readTextParts(parts: Array<{ type: string; text?: string }>): st
|
|
|
359
378
|
return text;
|
|
360
379
|
}
|
|
361
380
|
|
|
381
|
+
function toOpenAICompatibleUserContent(
|
|
382
|
+
parts: RuntimePromptUserContent,
|
|
383
|
+
): OpenAICompatibleUserContent {
|
|
384
|
+
if (!parts.some((part) => part.type !== "text" && part.mediaType.startsWith("image/"))) {
|
|
385
|
+
return readTextParts(parts);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const content: Exclude<OpenAICompatibleUserContent, string> = [];
|
|
389
|
+
|
|
390
|
+
for (const part of parts) {
|
|
391
|
+
if (part.type === "text") {
|
|
392
|
+
if (part.text.length > 0) {
|
|
393
|
+
content.push({ type: "text", text: part.text });
|
|
394
|
+
}
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (part.type === "image" || part.mediaType.startsWith("image/")) {
|
|
398
|
+
content.push({ type: "image_url", image_url: { url: part.url } });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return content.length > 0 ? content : readTextParts(parts);
|
|
403
|
+
}
|
|
404
|
+
|
|
362
405
|
export function toOpenAICompatibleMessages(
|
|
363
406
|
prompt: RuntimePromptMessage[],
|
|
364
407
|
): OpenAICompatibleChatMessage[] {
|
|
@@ -370,7 +413,7 @@ export function toOpenAICompatibleMessages(
|
|
|
370
413
|
messages.push({ role: "system", content: message.content });
|
|
371
414
|
break;
|
|
372
415
|
case "user":
|
|
373
|
-
messages.push({ role: "user", content:
|
|
416
|
+
messages.push({ role: "user", content: toOpenAICompatibleUserContent(message.content) });
|
|
374
417
|
break;
|
|
375
418
|
case "assistant": {
|
|
376
419
|
let text = "";
|
|
@@ -73,7 +73,13 @@ type EmbedManyOptions = {
|
|
|
73
73
|
|
|
74
74
|
type RuntimePromptMessage =
|
|
75
75
|
| { role: "system"; content: string }
|
|
76
|
-
| {
|
|
76
|
+
| {
|
|
77
|
+
role: "user";
|
|
78
|
+
content: Array<
|
|
79
|
+
| { type: "text"; text: string }
|
|
80
|
+
| { type: "image" | "file"; mediaType: string; url: string; filename?: string }
|
|
81
|
+
>;
|
|
82
|
+
}
|
|
77
83
|
| {
|
|
78
84
|
role: "assistant";
|
|
79
85
|
content: Array<
|
|
@@ -181,7 +187,9 @@ function toRuntimePrompt(
|
|
|
181
187
|
case "user":
|
|
182
188
|
prompt.push({
|
|
183
189
|
role: "user",
|
|
184
|
-
content:
|
|
190
|
+
content: typeof message.content === "string"
|
|
191
|
+
? [{ type: "text", text: message.content }]
|
|
192
|
+
: message.content,
|
|
185
193
|
});
|
|
186
194
|
break;
|
|
187
195
|
case "assistant":
|