veryfront 0.1.261 → 0.1.263
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/cli/commands/knowledge/command.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command.js +19 -26
- package/esm/cli/templates/manifest.d.ts +470 -470
- package/esm/cli/templates/manifest.js +519 -519
- package/esm/deno.js +1 -1
- package/esm/src/agent/ag-ui-detached-start.d.ts +1 -4
- package/esm/src/agent/ag-ui-detached-start.d.ts.map +1 -1
- package/esm/src/agent/ag-ui-detached-start.js +4 -67
- package/esm/src/agent/ag-ui-handler.d.ts +1 -4
- package/esm/src/agent/ag-ui-handler.d.ts.map +1 -1
- package/esm/src/agent/ag-ui-handler.js +3 -61
- package/esm/src/agent/ag-ui-host-support.d.ts.map +1 -1
- package/esm/src/agent/ag-ui-host-support.js +2 -21
- package/esm/src/agent/ag-ui-request-shared.d.ts +4 -0
- package/esm/src/agent/ag-ui-request-shared.d.ts.map +1 -0
- package/esm/src/agent/ag-ui-request-shared.js +47 -0
- package/esm/src/agent/ag-ui-run-control.d.ts.map +1 -1
- package/esm/src/agent/ag-ui-run-control.js +1 -23
- package/esm/src/agent/ag-ui-runtime-handler.d.ts +1 -5
- package/esm/src/agent/ag-ui-runtime-handler.d.ts.map +1 -1
- package/esm/src/agent/ag-ui-runtime-handler.js +3 -67
- package/esm/src/agent/ag-ui-tool-shared.d.ts +15 -0
- package/esm/src/agent/ag-ui-tool-shared.d.ts.map +1 -0
- package/esm/src/agent/ag-ui-tool-shared.js +47 -0
- package/esm/src/agent/conversation-run-event-preparation.d.ts +3 -1
- package/esm/src/agent/conversation-run-event-preparation.d.ts.map +1 -1
- package/esm/src/agent/conversation-run-event-preparation.js +40 -0
- package/esm/src/agent/index.d.ts +3 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +2 -1
- package/esm/src/agent/runtime-ag-ui-contract.d.ts.map +1 -1
- package/esm/src/agent/runtime-ag-ui-contract.js +2 -21
- package/esm/src/chat/chat-ui-message-helpers.d.ts +20 -0
- package/esm/src/chat/chat-ui-message-helpers.d.ts.map +1 -0
- package/esm/src/chat/chat-ui-message-helpers.js +167 -0
- package/esm/src/chat/index.d.ts +2 -0
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/chat/index.js +1 -0
- package/esm/src/chat/protocol.d.ts +109 -0
- package/esm/src/chat/protocol.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader/provider-http.d.ts +47 -0
- package/esm/src/provider/runtime-loader/provider-http.d.ts.map +1 -0
- package/esm/src/provider/runtime-loader/provider-http.js +171 -0
- package/esm/src/provider/runtime-loader/provider-records.d.ts +2 -0
- package/esm/src/provider/runtime-loader/provider-records.d.ts.map +1 -0
- package/esm/src/provider/runtime-loader/provider-records.js +6 -0
- package/esm/src/provider/runtime-loader.d.ts +2 -32
- package/esm/src/provider/runtime-loader.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader.js +3 -176
- package/esm/src/routing/api/module-loader/external-import-rewriter.d.ts +27 -0
- package/esm/src/routing/api/module-loader/external-import-rewriter.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/external-import-rewriter.js +339 -0
- package/esm/src/routing/api/module-loader/loader.d.ts +1 -22
- package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
- package/esm/src/routing/api/module-loader/loader.js +4 -336
- package/esm/src/server/dev-ui/manifest.d.ts +17 -17
- package/esm/src/server/dev-ui/manifest.js +17 -17
- 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/cli/commands/knowledge/command.ts +27 -32
- package/src/cli/templates/manifest.js +519 -519
- package/src/deno.js +1 -1
- package/src/src/agent/ag-ui-detached-start.ts +4 -92
- package/src/src/agent/ag-ui-handler.ts +3 -81
- package/src/src/agent/ag-ui-host-support.ts +5 -28
- package/src/src/agent/ag-ui-request-shared.ts +62 -0
- package/src/src/agent/ag-ui-run-control.ts +1 -27
- package/src/src/agent/ag-ui-runtime-handler.ts +3 -86
- package/src/src/agent/ag-ui-tool-shared.ts +77 -0
- package/src/src/agent/conversation-run-event-preparation.ts +57 -1
- package/src/src/agent/index.ts +19 -0
- package/src/src/agent/runtime-ag-ui-contract.ts +5 -28
- package/src/src/chat/chat-ui-message-helpers.ts +232 -0
- package/src/src/chat/index.ts +19 -0
- package/src/src/chat/protocol.ts +148 -0
- package/src/src/provider/runtime-loader/provider-http.ts +207 -0
- package/src/src/provider/runtime-loader/provider-records.ts +7 -0
- package/src/src/provider/runtime-loader.ts +10 -214
- package/src/src/routing/api/module-loader/external-import-rewriter.ts +461 -0
- package/src/src/routing/api/module-loader/loader.ts +19 -462
- package/src/src/server/dev-ui/manifest.js +17 -17
- package/src/src/utils/version-constant.ts +1 -1
package/src/src/agent/index.ts
CHANGED
|
@@ -273,8 +273,10 @@ export {
|
|
|
273
273
|
normalizeEncodedConversationRunEvents,
|
|
274
274
|
} from "./conversation-run-events.js";
|
|
275
275
|
export {
|
|
276
|
+
prepareConversationRunChunkEvents,
|
|
276
277
|
prepareConversationRunExternalEvents,
|
|
277
278
|
prepareConversationRunStreamEvents,
|
|
279
|
+
toConversationRunStreamEvent,
|
|
278
280
|
} from "./conversation-run-event-preparation.js";
|
|
279
281
|
export {
|
|
280
282
|
type ConversationRunMirror,
|
|
@@ -379,6 +381,23 @@ export {
|
|
|
379
381
|
streamDataStreamEvents,
|
|
380
382
|
stripLeadingEmptyObjectPlaceholder,
|
|
381
383
|
} from "./data-stream.js";
|
|
384
|
+
export type {
|
|
385
|
+
ChatMessageMetadata,
|
|
386
|
+
ChatMessageMetadataUsage,
|
|
387
|
+
ChatUiMessageChunk,
|
|
388
|
+
ChildRunAudit,
|
|
389
|
+
ChildRunAuditToolCall,
|
|
390
|
+
ChildRunAuditToolResult,
|
|
391
|
+
} from "../chat/protocol.js";
|
|
392
|
+
export {
|
|
393
|
+
buildChatStreamChunkMessageMetadata,
|
|
394
|
+
type BuildChatStreamChunkMessageMetadataInput,
|
|
395
|
+
dedupeChatUiMessageChunks,
|
|
396
|
+
extractChatMessageMetadata,
|
|
397
|
+
normalizeChatMessageMetadata,
|
|
398
|
+
normalizeChatUiMessageChunk,
|
|
399
|
+
normalizeChatUiMessageStream,
|
|
400
|
+
} from "../chat/chat-ui-message-helpers.js";
|
|
382
401
|
export {
|
|
383
402
|
expandAllowedRemoteToolNames,
|
|
384
403
|
getProviderNativeToolNames,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { parseAgUiJsonRequestOrError } from "./ag-ui-request-shared.js";
|
|
2
3
|
|
|
3
4
|
const AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
4
5
|
const MAX_TOOL_PARAMETERS_BYTES = 16_384;
|
|
@@ -172,32 +173,8 @@ export async function parseAgUiRuntimeRequest(request: Request): Promise<AgUiRun
|
|
|
172
173
|
export async function parseAgUiRuntimeRequestOrError(
|
|
173
174
|
request: Request,
|
|
174
175
|
): Promise<AgUiRuntimeRequest | Response> {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return Response.json(
|
|
180
|
-
{
|
|
181
|
-
error: "Invalid AG-UI runtime request",
|
|
182
|
-
details: error.issues.map((issue) => ({
|
|
183
|
-
path: issue.path,
|
|
184
|
-
message: issue.message,
|
|
185
|
-
})),
|
|
186
|
-
},
|
|
187
|
-
{ status: 400 },
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (error instanceof SyntaxError || error instanceof TypeError) {
|
|
192
|
-
return Response.json(
|
|
193
|
-
{
|
|
194
|
-
error: "Invalid AG-UI runtime request",
|
|
195
|
-
details: [{ path: [], message: "Malformed JSON request body" }],
|
|
196
|
-
},
|
|
197
|
-
{ status: 400 },
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
176
|
+
return await parseAgUiJsonRequestOrError(
|
|
177
|
+
() => parseAgUiRuntimeRequest(request),
|
|
178
|
+
"Invalid AG-UI runtime request",
|
|
179
|
+
);
|
|
203
180
|
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { ChatMessageMetadata, ChatUiMessageChunk } from "./protocol.js";
|
|
2
|
+
|
|
3
|
+
type StreamChunkMetadataPart = {
|
|
4
|
+
type: string;
|
|
5
|
+
totalUsage?: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface BuildChatStreamChunkMessageMetadataInput {
|
|
9
|
+
agentId: string;
|
|
10
|
+
modelId: string;
|
|
11
|
+
runId?: string;
|
|
12
|
+
streamingMessageId?: string;
|
|
13
|
+
part: StreamChunkMetadataPart;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ReplayState = {
|
|
17
|
+
content: string;
|
|
18
|
+
replayOffset: number | null;
|
|
19
|
+
started: boolean;
|
|
20
|
+
ended: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeUsageMetadata(value: unknown): ChatMessageMetadata["usage"] | undefined {
|
|
28
|
+
if (!isRecord(value)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const usage = {
|
|
33
|
+
...(typeof value.inputTokens === "number" ? { inputTokens: value.inputTokens } : {}),
|
|
34
|
+
...(typeof value.outputTokens === "number" ? { outputTokens: value.outputTokens } : {}),
|
|
35
|
+
...(typeof value.reasoningTokens === "number"
|
|
36
|
+
? { reasoningTokens: value.reasoningTokens }
|
|
37
|
+
: {}),
|
|
38
|
+
...(typeof value.cachedInputTokens === "number"
|
|
39
|
+
? { cachedInputTokens: value.cachedInputTokens }
|
|
40
|
+
: {}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function splitReplayDelta(
|
|
47
|
+
existing: string,
|
|
48
|
+
replayOffset: number,
|
|
49
|
+
delta: string,
|
|
50
|
+
): { emit: string; nextReplayOffset: number | null } {
|
|
51
|
+
const remaining = existing.slice(replayOffset);
|
|
52
|
+
|
|
53
|
+
if (!remaining) {
|
|
54
|
+
return { emit: delta, nextReplayOffset: null };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (delta === remaining.slice(0, delta.length)) {
|
|
58
|
+
return { emit: "", nextReplayOffset: replayOffset + delta.length };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (delta.startsWith(remaining)) {
|
|
62
|
+
return { emit: delta.slice(remaining.length), nextReplayOffset: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (remaining.startsWith(delta)) {
|
|
66
|
+
return { emit: "", nextReplayOffset: replayOffset + delta.length };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { emit: delta, nextReplayOffset: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getReplayState(stateMap: Map<string, ReplayState>, id: string): ReplayState {
|
|
73
|
+
const existing = stateMap.get(id);
|
|
74
|
+
if (existing) {
|
|
75
|
+
return existing;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const created: ReplayState = {
|
|
79
|
+
content: "",
|
|
80
|
+
replayOffset: null,
|
|
81
|
+
started: false,
|
|
82
|
+
ended: false,
|
|
83
|
+
};
|
|
84
|
+
stateMap.set(id, created);
|
|
85
|
+
return created;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function normalizeChatMessageMetadata(value: unknown): ChatMessageMetadata {
|
|
89
|
+
if (!isRecord(value)) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const usage = normalizeUsageMetadata(value.usage);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...(typeof value.createdAt === "string" ? { createdAt: value.createdAt } : {}),
|
|
97
|
+
...(typeof value.isStopped === "boolean" ? { isStopped: value.isStopped } : {}),
|
|
98
|
+
...(typeof value.isCompleted === "boolean" ? { isCompleted: value.isCompleted } : {}),
|
|
99
|
+
...(typeof value.completedAt === "string" ? { completedAt: value.completedAt } : {}),
|
|
100
|
+
...(typeof value.agentId === "string" ? { agentId: value.agentId } : {}),
|
|
101
|
+
...(typeof value.agentName === "string" ? { agentName: value.agentName } : {}),
|
|
102
|
+
...(typeof value.conversationId === "string" ? { conversationId: value.conversationId } : {}),
|
|
103
|
+
...(typeof value.modelId === "string" ? { modelId: value.modelId } : {}),
|
|
104
|
+
...(typeof value.runId === "string" ? { runId: value.runId } : {}),
|
|
105
|
+
...(typeof value.streamingMessageId === "string"
|
|
106
|
+
? { streamingMessageId: value.streamingMessageId }
|
|
107
|
+
: {}),
|
|
108
|
+
...(usage ? { usage } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function extractChatMessageMetadata(value: unknown): ChatMessageMetadata | undefined {
|
|
113
|
+
const normalized = normalizeChatMessageMetadata(value);
|
|
114
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildChatStreamChunkMessageMetadata(
|
|
118
|
+
input: BuildChatStreamChunkMessageMetadataInput,
|
|
119
|
+
): ChatMessageMetadata {
|
|
120
|
+
const baseMetadata: ChatMessageMetadata = {
|
|
121
|
+
agentId: input.agentId,
|
|
122
|
+
modelId: input.modelId,
|
|
123
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
124
|
+
...(input.streamingMessageId ? { streamingMessageId: input.streamingMessageId } : {}),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (input.part.type !== "finish" || !input.part.totalUsage) {
|
|
128
|
+
return baseMetadata;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const usage = normalizeUsageMetadata(input.part.totalUsage);
|
|
132
|
+
return usage ? { ...baseMetadata, usage } : baseMetadata;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function normalizeChatUiMessageChunk(
|
|
136
|
+
chunk: ChatUiMessageChunk<unknown>,
|
|
137
|
+
): ChatUiMessageChunk<ChatMessageMetadata> {
|
|
138
|
+
switch (chunk.type) {
|
|
139
|
+
case "start":
|
|
140
|
+
return {
|
|
141
|
+
type: "start",
|
|
142
|
+
...(chunk.messageId ? { messageId: chunk.messageId } : {}),
|
|
143
|
+
...(chunk.messageMetadata !== undefined
|
|
144
|
+
? { messageMetadata: normalizeChatMessageMetadata(chunk.messageMetadata) }
|
|
145
|
+
: {}),
|
|
146
|
+
};
|
|
147
|
+
case "message-metadata":
|
|
148
|
+
return {
|
|
149
|
+
type: "message-metadata",
|
|
150
|
+
messageMetadata: normalizeChatMessageMetadata(chunk.messageMetadata),
|
|
151
|
+
};
|
|
152
|
+
case "finish":
|
|
153
|
+
return {
|
|
154
|
+
type: "finish",
|
|
155
|
+
...(chunk.finishReason ? { finishReason: chunk.finishReason } : {}),
|
|
156
|
+
...(chunk.messageMetadata !== undefined
|
|
157
|
+
? { messageMetadata: normalizeChatMessageMetadata(chunk.messageMetadata) }
|
|
158
|
+
: {}),
|
|
159
|
+
};
|
|
160
|
+
default:
|
|
161
|
+
return chunk;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function* dedupeChatUiMessageChunks<TMessageMetadata>(
|
|
166
|
+
stream: AsyncIterable<ChatUiMessageChunk<TMessageMetadata>>,
|
|
167
|
+
): AsyncIterable<ChatUiMessageChunk<TMessageMetadata>> {
|
|
168
|
+
const textStates = new Map<string, ReplayState>();
|
|
169
|
+
const reasoningStates = new Map<string, ReplayState>();
|
|
170
|
+
|
|
171
|
+
for await (const chunk of stream) {
|
|
172
|
+
if (chunk.type === "text-start" || chunk.type === "reasoning-start") {
|
|
173
|
+
const stateMap = chunk.type === "text-start" ? textStates : reasoningStates;
|
|
174
|
+
const state = getReplayState(stateMap, chunk.id);
|
|
175
|
+
|
|
176
|
+
if (state.started) {
|
|
177
|
+
state.replayOffset = 0;
|
|
178
|
+
state.ended = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
state.started = true;
|
|
183
|
+
state.ended = false;
|
|
184
|
+
yield chunk;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (chunk.type === "text-delta" || chunk.type === "reasoning-delta") {
|
|
189
|
+
const stateMap = chunk.type === "text-delta" ? textStates : reasoningStates;
|
|
190
|
+
const state = getReplayState(stateMap, chunk.id);
|
|
191
|
+
const { emit, nextReplayOffset } = state.replayOffset === null
|
|
192
|
+
? { emit: chunk.delta, nextReplayOffset: null as number | null }
|
|
193
|
+
: splitReplayDelta(state.content, state.replayOffset, chunk.delta);
|
|
194
|
+
|
|
195
|
+
state.replayOffset = nextReplayOffset;
|
|
196
|
+
if (!emit) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
state.content += emit;
|
|
201
|
+
yield {
|
|
202
|
+
...chunk,
|
|
203
|
+
delta: emit,
|
|
204
|
+
};
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (chunk.type === "text-end" || chunk.type === "reasoning-end") {
|
|
209
|
+
const stateMap = chunk.type === "text-end" ? textStates : reasoningStates;
|
|
210
|
+
const state = stateMap.get(chunk.id);
|
|
211
|
+
|
|
212
|
+
if (!state || state.ended) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
state.replayOffset = null;
|
|
217
|
+
state.ended = true;
|
|
218
|
+
yield chunk;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
yield chunk;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function* normalizeChatUiMessageStream(
|
|
227
|
+
stream: AsyncIterable<ChatUiMessageChunk<unknown>>,
|
|
228
|
+
): AsyncIterable<ChatUiMessageChunk<ChatMessageMetadata>> {
|
|
229
|
+
for await (const chunk of dedupeChatUiMessageChunks(stream)) {
|
|
230
|
+
yield normalizeChatUiMessageChunk(chunk);
|
|
231
|
+
}
|
|
232
|
+
}
|
package/src/src/chat/index.ts
CHANGED
|
@@ -212,12 +212,31 @@ export {
|
|
|
212
212
|
type UseChatResult,
|
|
213
213
|
} from "../agent/react/use-chat/index.js";
|
|
214
214
|
|
|
215
|
+
export type {
|
|
216
|
+
ChatMessageMetadata,
|
|
217
|
+
ChatMessageMetadataUsage,
|
|
218
|
+
ChatUiMessageChunk,
|
|
219
|
+
ChildRunAudit,
|
|
220
|
+
ChildRunAuditToolCall,
|
|
221
|
+
ChildRunAuditToolResult,
|
|
222
|
+
} from "./protocol.js";
|
|
223
|
+
|
|
215
224
|
export {
|
|
216
225
|
useAgent,
|
|
217
226
|
type UseAgentOptions,
|
|
218
227
|
type UseAgentResult,
|
|
219
228
|
} from "../agent/react/use-agent.js";
|
|
220
229
|
|
|
230
|
+
export {
|
|
231
|
+
buildChatStreamChunkMessageMetadata,
|
|
232
|
+
type BuildChatStreamChunkMessageMetadataInput,
|
|
233
|
+
dedupeChatUiMessageChunks,
|
|
234
|
+
extractChatMessageMetadata,
|
|
235
|
+
normalizeChatMessageMetadata,
|
|
236
|
+
normalizeChatUiMessageChunk,
|
|
237
|
+
normalizeChatUiMessageStream,
|
|
238
|
+
} from "./chat-ui-message-helpers.js";
|
|
239
|
+
|
|
221
240
|
export {
|
|
222
241
|
useCompletion,
|
|
223
242
|
type UseCompletionOptions,
|
package/src/src/chat/protocol.ts
CHANGED
|
@@ -77,6 +77,52 @@ export interface ChatMessage {
|
|
|
77
77
|
createdAt?: Date | string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export interface ChatMessageMetadataUsage {
|
|
81
|
+
inputTokens?: number;
|
|
82
|
+
outputTokens?: number;
|
|
83
|
+
reasoningTokens?: number;
|
|
84
|
+
cachedInputTokens?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ChildRunAuditToolCall {
|
|
88
|
+
toolName: string;
|
|
89
|
+
toolCallId: string;
|
|
90
|
+
input?: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ChildRunAuditToolResult {
|
|
94
|
+
toolName: string;
|
|
95
|
+
toolCallId: string;
|
|
96
|
+
input: unknown;
|
|
97
|
+
output: unknown;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ChildRunAudit {
|
|
101
|
+
status: "completed" | "failed" | "cancelled" | "stopped";
|
|
102
|
+
description?: string;
|
|
103
|
+
steps?: number;
|
|
104
|
+
durationMs?: number;
|
|
105
|
+
toolCalls?: ChildRunAuditToolCall[];
|
|
106
|
+
toolResults?: ChildRunAuditToolResult[];
|
|
107
|
+
terminalErrorCode?: string | null;
|
|
108
|
+
terminalErrorMessage?: string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface ChatMessageMetadata {
|
|
112
|
+
createdAt?: string;
|
|
113
|
+
isStopped?: boolean;
|
|
114
|
+
isCompleted?: boolean;
|
|
115
|
+
completedAt?: string;
|
|
116
|
+
agentId?: string;
|
|
117
|
+
agentName?: string;
|
|
118
|
+
conversationId?: string;
|
|
119
|
+
modelId?: string;
|
|
120
|
+
runId?: string;
|
|
121
|
+
streamingMessageId?: string;
|
|
122
|
+
childRunAudit?: ChildRunAudit;
|
|
123
|
+
usage?: ChatMessageMetadataUsage;
|
|
124
|
+
}
|
|
125
|
+
|
|
80
126
|
export type ChatFinishReason =
|
|
81
127
|
| "stop"
|
|
82
128
|
| "length"
|
|
@@ -208,3 +254,105 @@ export type ChatStreamEvent =
|
|
|
208
254
|
type: "error";
|
|
209
255
|
errorText: string;
|
|
210
256
|
};
|
|
257
|
+
|
|
258
|
+
type MessageLifecycleChunk<TMessageMetadata> =
|
|
259
|
+
| {
|
|
260
|
+
type: "start";
|
|
261
|
+
messageId?: string;
|
|
262
|
+
messageMetadata?: TMessageMetadata;
|
|
263
|
+
}
|
|
264
|
+
| {
|
|
265
|
+
type: "finish";
|
|
266
|
+
finishReason?: string;
|
|
267
|
+
messageMetadata?: TMessageMetadata;
|
|
268
|
+
}
|
|
269
|
+
| {
|
|
270
|
+
type: "message-metadata";
|
|
271
|
+
messageMetadata: TMessageMetadata;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
type IdChunk<TType extends string> = {
|
|
275
|
+
type: TType;
|
|
276
|
+
id: string;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
type IdDeltaChunk<TType extends string> = IdChunk<TType> & {
|
|
280
|
+
delta: string;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
type ToolCallChunk<TType extends string> = {
|
|
284
|
+
type: TType;
|
|
285
|
+
toolCallId: string;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
type NamedToolCallChunk<TType extends string> = ToolCallChunk<TType> & {
|
|
289
|
+
toolName: string;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
type ToolInputChunk<TType extends string> = NamedToolCallChunk<TType> & {
|
|
293
|
+
input: unknown;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
type ToolErrorChunk<TType extends string> = ToolCallChunk<TType> & {
|
|
297
|
+
errorText: string;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export type ChatUiMessageChunk<TMessageMetadata = ChatMessageMetadata> =
|
|
301
|
+
| MessageLifecycleChunk<TMessageMetadata>
|
|
302
|
+
| {
|
|
303
|
+
type: "start-step";
|
|
304
|
+
}
|
|
305
|
+
| {
|
|
306
|
+
type: "finish-step";
|
|
307
|
+
}
|
|
308
|
+
| {
|
|
309
|
+
type: "abort";
|
|
310
|
+
}
|
|
311
|
+
| IdChunk<"reasoning-start">
|
|
312
|
+
| IdDeltaChunk<"reasoning-delta">
|
|
313
|
+
| IdChunk<"reasoning-end">
|
|
314
|
+
| IdChunk<"text-start">
|
|
315
|
+
| IdDeltaChunk<"text-delta">
|
|
316
|
+
| IdChunk<"text-end">
|
|
317
|
+
| {
|
|
318
|
+
type: "source-url";
|
|
319
|
+
sourceId: string;
|
|
320
|
+
url: string;
|
|
321
|
+
title?: string;
|
|
322
|
+
}
|
|
323
|
+
| {
|
|
324
|
+
type: "source-document";
|
|
325
|
+
sourceId: string;
|
|
326
|
+
mediaType: string;
|
|
327
|
+
title: string;
|
|
328
|
+
filename?: string;
|
|
329
|
+
}
|
|
330
|
+
| {
|
|
331
|
+
type: "file";
|
|
332
|
+
mediaType: string;
|
|
333
|
+
url: string;
|
|
334
|
+
}
|
|
335
|
+
| NamedToolCallChunk<"tool-input-start">
|
|
336
|
+
| (ToolCallChunk<"tool-input-delta"> & {
|
|
337
|
+
inputTextDelta: string;
|
|
338
|
+
})
|
|
339
|
+
| ToolInputChunk<"tool-input-available">
|
|
340
|
+
| (ToolInputChunk<"tool-input-error"> & {
|
|
341
|
+
errorText: string;
|
|
342
|
+
})
|
|
343
|
+
| (ToolCallChunk<"tool-output-available"> & {
|
|
344
|
+
output: unknown;
|
|
345
|
+
})
|
|
346
|
+
| ToolErrorChunk<"tool-output-error">
|
|
347
|
+
| ToolCallChunk<"tool-output-denied">
|
|
348
|
+
| (ToolCallChunk<"tool-approval-request"> & {
|
|
349
|
+
approvalId: string;
|
|
350
|
+
})
|
|
351
|
+
| {
|
|
352
|
+
type: "error";
|
|
353
|
+
errorText: string;
|
|
354
|
+
}
|
|
355
|
+
| {
|
|
356
|
+
type: `data-${string}`;
|
|
357
|
+
data: unknown;
|
|
358
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { readRecord } from "./provider-records.js";
|
|
2
|
+
|
|
3
|
+
export type ProviderKind = "anthropic" | "openai" | "google";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base class for typed provider errors. The `retryable` flag is the
|
|
7
|
+
* primary signal for callers (or a retry wrapper) to decide whether to
|
|
8
|
+
* re-issue the request. `retryAfterMs` is set when the provider gave an
|
|
9
|
+
* explicit delay hint (Retry-After header, Retry-Info trailer).
|
|
10
|
+
*/
|
|
11
|
+
export class ProviderError extends Error {
|
|
12
|
+
readonly provider: ProviderKind;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly retryable: boolean;
|
|
15
|
+
readonly retryAfterMs?: number;
|
|
16
|
+
|
|
17
|
+
constructor(options: {
|
|
18
|
+
provider: ProviderKind;
|
|
19
|
+
status: number;
|
|
20
|
+
message: string;
|
|
21
|
+
retryable: boolean;
|
|
22
|
+
retryAfterMs?: number;
|
|
23
|
+
}) {
|
|
24
|
+
super(options.message);
|
|
25
|
+
this.name = new.target.name;
|
|
26
|
+
this.provider = options.provider;
|
|
27
|
+
this.status = options.status;
|
|
28
|
+
this.retryable = options.retryable;
|
|
29
|
+
if (options.retryAfterMs !== undefined) {
|
|
30
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Provider reports it is overloaded (Anthropic 529, OpenAI/Google 503). */
|
|
36
|
+
export class ProviderOverloadedError extends ProviderError {}
|
|
37
|
+
|
|
38
|
+
/** Provider is rate limiting this API key (OpenAI/Google 429 with Retry-After). */
|
|
39
|
+
export class ProviderRateLimitError extends ProviderError {}
|
|
40
|
+
|
|
41
|
+
/** Provider account quota is exhausted — non-retryable. */
|
|
42
|
+
export class ProviderQuotaError extends ProviderError {}
|
|
43
|
+
|
|
44
|
+
/** Non-retryable 4xx/5xx that doesn't fit another bucket. */
|
|
45
|
+
export class ProviderRequestError extends ProviderError {}
|
|
46
|
+
|
|
47
|
+
function parseRetryAfterMs(header: string | null): number | undefined {
|
|
48
|
+
if (!header) return undefined;
|
|
49
|
+
const asNumber = Number(header);
|
|
50
|
+
if (Number.isFinite(asNumber) && asNumber >= 0) {
|
|
51
|
+
return Math.round(asNumber * 1000);
|
|
52
|
+
}
|
|
53
|
+
// HTTP-date form (rare in practice for LLM providers).
|
|
54
|
+
const parsed = Date.parse(header);
|
|
55
|
+
if (!Number.isNaN(parsed)) {
|
|
56
|
+
return Math.max(0, parsed - Date.now());
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Inspect a non-2xx response and build the most specific ProviderError
|
|
63
|
+
* subclass we can. Reads the response body as text (it's already dead
|
|
64
|
+
* on the wire by this point). Body classification handles the cases
|
|
65
|
+
* where HTTP status alone is ambiguous — notably OpenAI
|
|
66
|
+
* `insufficient_quota` vs `rate_limit_exceeded` both arriving as 429.
|
|
67
|
+
*/
|
|
68
|
+
async function buildProviderError(
|
|
69
|
+
provider: ProviderKind,
|
|
70
|
+
response: Response,
|
|
71
|
+
): Promise<ProviderError> {
|
|
72
|
+
const rawBody = await response.text();
|
|
73
|
+
const message = rawBody.trim() || `${response.status} ${response.statusText}`.trim();
|
|
74
|
+
const status = response.status;
|
|
75
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
76
|
+
|
|
77
|
+
const parsedBody = (() => {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(rawBody) as Record<string, unknown>;
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
const errorRecord = readRecord(parsedBody?.error);
|
|
85
|
+
const errorCode = typeof errorRecord?.code === "string"
|
|
86
|
+
? errorRecord.code
|
|
87
|
+
: typeof errorRecord?.type === "string"
|
|
88
|
+
? errorRecord.type
|
|
89
|
+
: typeof errorRecord?.status === "string"
|
|
90
|
+
? errorRecord.status
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
// Anthropic 529 = overloaded. Anthropic surfaces this with
|
|
94
|
+
// { error: { type: "overloaded_error" } } in the body.
|
|
95
|
+
if (provider === "anthropic" && status === 529) {
|
|
96
|
+
return new ProviderOverloadedError({
|
|
97
|
+
provider,
|
|
98
|
+
status,
|
|
99
|
+
message,
|
|
100
|
+
retryable: true,
|
|
101
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// OpenAI / Google 503 = overloaded.
|
|
106
|
+
if ((provider === "openai" || provider === "google") && status === 503) {
|
|
107
|
+
return new ProviderOverloadedError({
|
|
108
|
+
provider,
|
|
109
|
+
status,
|
|
110
|
+
message,
|
|
111
|
+
retryable: true,
|
|
112
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// OpenAI 429 splits based on the error code in the body:
|
|
117
|
+
// - insufficient_quota → hard quota, non-retryable
|
|
118
|
+
// - rate_limit_exceeded / tokens_per_min_exceeded → retry with Retry-After
|
|
119
|
+
if (provider === "openai" && status === 429) {
|
|
120
|
+
if (errorCode === "insufficient_quota") {
|
|
121
|
+
return new ProviderQuotaError({
|
|
122
|
+
provider,
|
|
123
|
+
status,
|
|
124
|
+
message,
|
|
125
|
+
retryable: false,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return new ProviderRateLimitError({
|
|
129
|
+
provider,
|
|
130
|
+
status,
|
|
131
|
+
message,
|
|
132
|
+
retryable: true,
|
|
133
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Google 429 RESOURCE_EXHAUSTED is almost always the daily free-tier
|
|
138
|
+
// quota — surface as a hard quota error so callers don't hot-loop on
|
|
139
|
+
// retries that can't possibly succeed until midnight UTC.
|
|
140
|
+
if (provider === "google" && status === 429) {
|
|
141
|
+
if (errorCode === "RESOURCE_EXHAUSTED") {
|
|
142
|
+
return new ProviderQuotaError({
|
|
143
|
+
provider,
|
|
144
|
+
status,
|
|
145
|
+
message,
|
|
146
|
+
retryable: false,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return new ProviderRateLimitError({
|
|
150
|
+
provider,
|
|
151
|
+
status,
|
|
152
|
+
message,
|
|
153
|
+
retryable: true,
|
|
154
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return new ProviderRequestError({
|
|
159
|
+
provider,
|
|
160
|
+
status,
|
|
161
|
+
message,
|
|
162
|
+
retryable: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function requestJson(options: {
|
|
167
|
+
url: string;
|
|
168
|
+
fetchImpl: typeof globalThis.fetch;
|
|
169
|
+
init: RequestInit;
|
|
170
|
+
providerLabel: string;
|
|
171
|
+
providerKind: ProviderKind;
|
|
172
|
+
}): Promise<unknown> {
|
|
173
|
+
const response = await options.fetchImpl(options.url, options.init);
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
const err = await buildProviderError(options.providerKind, response);
|
|
176
|
+
err.message = `${options.providerLabel} request failed: ${err.message}`;
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return response.json();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function requestStream(options: {
|
|
184
|
+
url: string;
|
|
185
|
+
fetchImpl: typeof globalThis.fetch;
|
|
186
|
+
init: RequestInit;
|
|
187
|
+
providerLabel: string;
|
|
188
|
+
providerKind: ProviderKind;
|
|
189
|
+
}): Promise<ReadableStream<Uint8Array>> {
|
|
190
|
+
const response = await options.fetchImpl(options.url, options.init);
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const err = await buildProviderError(options.providerKind, response);
|
|
193
|
+
err.message = `${options.providerLabel} request failed: ${err.message}`;
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!response.body) {
|
|
198
|
+
throw new ProviderRequestError({
|
|
199
|
+
provider: options.providerKind,
|
|
200
|
+
status: response.status,
|
|
201
|
+
message: `${options.providerLabel} request failed: stream body missing`,
|
|
202
|
+
retryable: false,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return response.body;
|
|
207
|
+
}
|