@superblocksteam/clark 2.0.0-SNAPSHOT.a1e14d6b7a1bcd87
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/LICENSE.txt +87 -0
- package/dist/clark-chat-settings.d.ts +11 -0
- package/dist/clark-chat-settings.d.ts.map +1 -0
- package/dist/clark-chat-settings.js +12 -0
- package/dist/clark-chat-settings.js.map +1 -0
- package/dist/clark-error-handler.d.ts +17 -0
- package/dist/clark-error-handler.d.ts.map +1 -0
- package/dist/clark-error-handler.js +21 -0
- package/dist/clark-error-handler.js.map +1 -0
- package/dist/clark-language-model.d.ts +67 -0
- package/dist/clark-language-model.d.ts.map +1 -0
- package/dist/clark-language-model.js +862 -0
- package/dist/clark-language-model.js.map +1 -0
- package/dist/clark-provider.d.ts +66 -0
- package/dist/clark-provider.d.ts.map +1 -0
- package/dist/clark-provider.js +72 -0
- package/dist/clark-provider.js.map +1 -0
- package/dist/clark-stream-telemetry.d.ts +72 -0
- package/dist/clark-stream-telemetry.d.ts.map +1 -0
- package/dist/clark-stream-telemetry.js +22 -0
- package/dist/clark-stream-telemetry.js.map +1 -0
- package/dist/content-block-utils.d.ts +55 -0
- package/dist/content-block-utils.d.ts.map +1 -0
- package/dist/content-block-utils.js +122 -0
- package/dist/content-block-utils.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/docs-mcp.d.ts +23 -0
- package/dist/mcp/docs-mcp.d.ts.map +1 -0
- package/dist/mcp/docs-mcp.js +95 -0
- package/dist/mcp/docs-mcp.js.map +1 -0
- package/dist/mcp/extract-mcp-text-content.d.ts +11 -0
- package/dist/mcp/extract-mcp-text-content.d.ts.map +1 -0
- package/dist/mcp/extract-mcp-text-content.js +37 -0
- package/dist/mcp/extract-mcp-text-content.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-client.d.ts +52 -0
- package/dist/mcp/mcp-client.d.ts.map +1 -0
- package/dist/mcp/mcp-client.js +164 -0
- package/dist/mcp/mcp-client.js.map +1 -0
- package/dist/mcp/types.d.ts +55 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +8 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/snowflake-cortex-fetch.d.ts +21 -0
- package/dist/snowflake-cortex-fetch.d.ts.map +1 -0
- package/dist/snowflake-cortex-fetch.js +349 -0
- package/dist/snowflake-cortex-fetch.js.map +1 -0
- package/dist/stall-timeout.d.ts +5 -0
- package/dist/stall-timeout.d.ts.map +1 -0
- package/dist/stall-timeout.js +30 -0
- package/dist/stall-timeout.js.map +1 -0
- package/dist/thinking-options.d.ts +18 -0
- package/dist/thinking-options.d.ts.map +1 -0
- package/dist/thinking-options.js +53 -0
- package/dist/thinking-options.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
import { EmptyResponseBodyError, JSONParseError, } from "@ai-sdk/provider";
|
|
2
|
+
import { combineHeaders, extractResponseHeaders, postJsonToApi, } from "@ai-sdk/provider-utils";
|
|
3
|
+
import { EventSourceParserStream, } from "eventsource-parser/stream";
|
|
4
|
+
import { clarkFailedResponseHandler } from "./clark-error-handler.js";
|
|
5
|
+
import { serializeToolInput, unwrapToolInputDelta, } from "./content-block-utils.js";
|
|
6
|
+
function toFinishReason(rawFinishReason) {
|
|
7
|
+
const normalized = rawFinishReason === "stop" ||
|
|
8
|
+
rawFinishReason === "length" ||
|
|
9
|
+
rawFinishReason === "content-filter" ||
|
|
10
|
+
rawFinishReason === "tool-calls" ||
|
|
11
|
+
rawFinishReason === "error" ||
|
|
12
|
+
rawFinishReason === "other"
|
|
13
|
+
? rawFinishReason
|
|
14
|
+
: "other";
|
|
15
|
+
return { unified: normalized, raw: rawFinishReason };
|
|
16
|
+
}
|
|
17
|
+
function toUsage(usageData) {
|
|
18
|
+
const inputTotal = usageData?.inputTokens ?? usageData?.promptTokens;
|
|
19
|
+
const cacheRead = usageData?.inputTokenDetails?.cacheReadTokens ??
|
|
20
|
+
usageData?.cachedInputTokens;
|
|
21
|
+
const cacheWrite = usageData?.inputTokenDetails?.cacheWriteTokens;
|
|
22
|
+
const noCacheInput = usageData?.inputTokenDetails?.noCacheTokens ??
|
|
23
|
+
(inputTotal !== undefined
|
|
24
|
+
? Math.max(0, inputTotal - (cacheRead ?? 0) - (cacheWrite ?? 0))
|
|
25
|
+
: undefined);
|
|
26
|
+
const outputTotal = usageData?.outputTokens ?? usageData?.completionTokens;
|
|
27
|
+
return {
|
|
28
|
+
inputTokens: {
|
|
29
|
+
total: inputTotal,
|
|
30
|
+
noCache: noCacheInput,
|
|
31
|
+
cacheRead: cacheRead,
|
|
32
|
+
cacheWrite: cacheWrite,
|
|
33
|
+
},
|
|
34
|
+
outputTokens: {
|
|
35
|
+
total: outputTotal,
|
|
36
|
+
text: outputTotal,
|
|
37
|
+
reasoning: undefined,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function asRecord(value) {
|
|
42
|
+
if (typeof value === "object" && value !== null) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function asProviderMetadata(value) {
|
|
48
|
+
const record = asRecord(value);
|
|
49
|
+
if (record == null) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return record;
|
|
53
|
+
}
|
|
54
|
+
function getUnifiedRoutingOptions(providerOptions) {
|
|
55
|
+
const unifiedOptions = asRecord(providerOptions?.unified);
|
|
56
|
+
if (!unifiedOptions) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const routingOptions = {
|
|
60
|
+
...(typeof unifiedOptions.coreModel === "string" && {
|
|
61
|
+
coreModel: unifiedOptions.coreModel,
|
|
62
|
+
}),
|
|
63
|
+
...(typeof unifiedOptions.forceProvider === "string" && {
|
|
64
|
+
forceProvider: unifiedOptions.forceProvider,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
return Object.keys(routingOptions).length > 0 ? routingOptions : undefined;
|
|
68
|
+
}
|
|
69
|
+
function sanitizeProviderOptions(providerOptions) {
|
|
70
|
+
if (!providerOptions || !("unified" in providerOptions)) {
|
|
71
|
+
return providerOptions;
|
|
72
|
+
}
|
|
73
|
+
const { unified: _unified, ...rest } = providerOptions;
|
|
74
|
+
const unifiedOptions = getUnifiedRoutingOptions(providerOptions);
|
|
75
|
+
return unifiedOptions
|
|
76
|
+
? { ...rest, unified: unifiedOptions }
|
|
77
|
+
: rest;
|
|
78
|
+
}
|
|
79
|
+
function asString(value) {
|
|
80
|
+
return typeof value === "string" ? value : undefined;
|
|
81
|
+
}
|
|
82
|
+
function headerValue(headers, name) {
|
|
83
|
+
if (!headers) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const normalizedName = name.toLowerCase();
|
|
87
|
+
if (headers instanceof Headers) {
|
|
88
|
+
return headers.get(normalizedName) ?? undefined;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(headers)) {
|
|
91
|
+
return headers.find(([key]) => key.toLowerCase() === normalizedName)?.[1];
|
|
92
|
+
}
|
|
93
|
+
return Object.entries(headers).find(([key]) => key.toLowerCase() === normalizedName)?.[1];
|
|
94
|
+
}
|
|
95
|
+
function requestFailure(error, abortSignalAborted) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
return {
|
|
98
|
+
name: error.name,
|
|
99
|
+
message: error.message,
|
|
100
|
+
abortSignalAborted,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
message: String(error),
|
|
105
|
+
abortSignalAborted,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function asWarnings(value) {
|
|
109
|
+
if (!Array.isArray(value)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function asUsage(value) {
|
|
115
|
+
const record = asRecord(value);
|
|
116
|
+
if (!record) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
inputTokens: typeof record.inputTokens === "number" ? record.inputTokens : undefined,
|
|
121
|
+
outputTokens: typeof record.outputTokens === "number" ? record.outputTokens : undefined,
|
|
122
|
+
totalTokens: typeof record.totalTokens === "number" ? record.totalTokens : undefined,
|
|
123
|
+
promptTokens: typeof record.promptTokens === "number" ? record.promptTokens : undefined,
|
|
124
|
+
completionTokens: typeof record.completionTokens === "number"
|
|
125
|
+
? record.completionTokens
|
|
126
|
+
: undefined,
|
|
127
|
+
cachedInputTokens: typeof record.cachedInputTokens === "number"
|
|
128
|
+
? record.cachedInputTokens
|
|
129
|
+
: undefined,
|
|
130
|
+
inputTokenDetails: asRecord(record.inputTokenDetails)
|
|
131
|
+
? {
|
|
132
|
+
noCacheTokens: typeof asRecord(record.inputTokenDetails)?.noCacheTokens ===
|
|
133
|
+
"number"
|
|
134
|
+
? asRecord(record.inputTokenDetails)?.noCacheTokens
|
|
135
|
+
: undefined,
|
|
136
|
+
cacheReadTokens: typeof asRecord(record.inputTokenDetails)?.cacheReadTokens ===
|
|
137
|
+
"number"
|
|
138
|
+
? asRecord(record.inputTokenDetails)?.cacheReadTokens
|
|
139
|
+
: undefined,
|
|
140
|
+
cacheWriteTokens: typeof asRecord(record.inputTokenDetails)?.cacheWriteTokens ===
|
|
141
|
+
"number"
|
|
142
|
+
? asRecord(record.inputTokenDetails)?.cacheWriteTokens
|
|
143
|
+
: undefined,
|
|
144
|
+
}
|
|
145
|
+
: undefined,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Wrap the debug hooks so a buggy sink that throws can never break streaming.
|
|
150
|
+
* Each hook swallows its own errors so the stream keeps flowing no matter what.
|
|
151
|
+
*/
|
|
152
|
+
function guardTelemetryScope(scope) {
|
|
153
|
+
return {
|
|
154
|
+
rawParsed() {
|
|
155
|
+
try {
|
|
156
|
+
scope.rawParsed();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// logging must never break streaming
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
unknownChunk(chunkType) {
|
|
163
|
+
try {
|
|
164
|
+
scope.unknownChunk(chunkType);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// logging must never break streaming
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
parseError(dataLength) {
|
|
171
|
+
try {
|
|
172
|
+
scope.parseError(dataLength);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// logging must never break streaming
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
errorChunk(detail) {
|
|
179
|
+
try {
|
|
180
|
+
scope.errorChunk(detail);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// logging must never break streaming
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
requestFailed(detail) {
|
|
187
|
+
try {
|
|
188
|
+
scope.requestFailed?.(detail);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// logging must never break streaming
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
streamFailed(detail) {
|
|
195
|
+
try {
|
|
196
|
+
scope.streamFailed?.(detail);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// logging must never break streaming
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function reportStreamReadFailures(source, telemetryScope, abortSignal) {
|
|
205
|
+
const reader = source.getReader();
|
|
206
|
+
return new ReadableStream({
|
|
207
|
+
async pull(controller) {
|
|
208
|
+
try {
|
|
209
|
+
const result = await reader.read();
|
|
210
|
+
if (result.done) {
|
|
211
|
+
controller.close();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
controller.enqueue(result.value);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
telemetryScope?.streamFailed?.(requestFailure(error, abortSignal?.aborted === true));
|
|
218
|
+
controller.error(error);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
async cancel(reason) {
|
|
222
|
+
await reader.cancel(reason);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function forwardAsActivity(controller, value) {
|
|
227
|
+
// We can't turn this message into normal content, but it still proves the
|
|
228
|
+
// stream is alive. Pass it through as "raw" so the client's stall timer
|
|
229
|
+
// doesn't wrongly decide the stream went dead, and so we never show the
|
|
230
|
+
// unparseable message as content.
|
|
231
|
+
controller.enqueue({ type: "raw", rawValue: value });
|
|
232
|
+
}
|
|
233
|
+
class DefaultClarkRequestBuilder {
|
|
234
|
+
async build({ prompt, maxOutputTokens, temperature, topP, topK, frequencyPenalty, presencePenalty, stopSequences, responseFormat, seed, tools, toolChoice, providerOptions, }, modelId) {
|
|
235
|
+
const warnings = [];
|
|
236
|
+
if (topP != null) {
|
|
237
|
+
warnings.push({
|
|
238
|
+
type: "unsupported",
|
|
239
|
+
feature: "topP",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (topK != null) {
|
|
243
|
+
warnings.push({
|
|
244
|
+
type: "unsupported",
|
|
245
|
+
feature: "topK",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (frequencyPenalty != null) {
|
|
249
|
+
warnings.push({
|
|
250
|
+
type: "unsupported",
|
|
251
|
+
feature: "frequencyPenalty",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (presencePenalty != null) {
|
|
255
|
+
warnings.push({
|
|
256
|
+
type: "unsupported",
|
|
257
|
+
feature: "presencePenalty",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (stopSequences != null) {
|
|
261
|
+
warnings.push({
|
|
262
|
+
type: "unsupported",
|
|
263
|
+
feature: "stopSequences",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
let outputSchema = undefined;
|
|
267
|
+
if (responseFormat != null) {
|
|
268
|
+
if (responseFormat.type === "json" && responseFormat.schema) {
|
|
269
|
+
outputSchema = responseFormat.schema;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
warnings.push({
|
|
273
|
+
type: "unsupported",
|
|
274
|
+
feature: "responseFormat",
|
|
275
|
+
details: "Only JSON response format with schema is supported",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (seed != null) {
|
|
280
|
+
warnings.push({
|
|
281
|
+
type: "unsupported",
|
|
282
|
+
feature: "seed",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const providerOptionsForRequest = sanitizeProviderOptions(providerOptions);
|
|
286
|
+
const unifiedOptions = getUnifiedRoutingOptions(providerOptionsForRequest);
|
|
287
|
+
const baseArgs = {
|
|
288
|
+
model: modelId,
|
|
289
|
+
max_tokens: maxOutputTokens,
|
|
290
|
+
messages: prompt,
|
|
291
|
+
temperature: temperature,
|
|
292
|
+
providerOptions: providerOptionsForRequest,
|
|
293
|
+
...(unifiedOptions?.forceProvider && {
|
|
294
|
+
forceProvider: unifiedOptions.forceProvider,
|
|
295
|
+
}),
|
|
296
|
+
...(unifiedOptions?.coreModel && { coreModel: unifiedOptions.coreModel }),
|
|
297
|
+
};
|
|
298
|
+
const finalArgs = {
|
|
299
|
+
...baseArgs,
|
|
300
|
+
tools,
|
|
301
|
+
toolChoice,
|
|
302
|
+
};
|
|
303
|
+
if (outputSchema !== undefined) {
|
|
304
|
+
finalArgs.outputSchema = outputSchema;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
args: finalArgs,
|
|
308
|
+
warnings,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
class DefaultClarkTransport {
|
|
313
|
+
async postJson({ url, headers, body, failedResponseHandler, successfulResponseHandler, abortSignal, fetch, }) {
|
|
314
|
+
const result = await postJsonToApi({
|
|
315
|
+
url,
|
|
316
|
+
headers,
|
|
317
|
+
body,
|
|
318
|
+
failedResponseHandler,
|
|
319
|
+
successfulResponseHandler,
|
|
320
|
+
abortSignal,
|
|
321
|
+
fetch,
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
responseHeaders: result.responseHeaders ?? {},
|
|
325
|
+
value: result.value,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function mergeWarnings(localWarnings, responseWarnings) {
|
|
330
|
+
const mergedWarnings = [...localWarnings];
|
|
331
|
+
const warningKeys = new Set(localWarnings.map((warning) => JSON.stringify(warning)));
|
|
332
|
+
for (const warning of responseWarnings ?? []) {
|
|
333
|
+
const warningKey = JSON.stringify(warning);
|
|
334
|
+
if (warningKeys.has(warningKey)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
warningKeys.add(warningKey);
|
|
338
|
+
mergedWarnings.push(warning);
|
|
339
|
+
}
|
|
340
|
+
return mergedWarnings;
|
|
341
|
+
}
|
|
342
|
+
export class ClarkChatLanguageModel {
|
|
343
|
+
specificationVersion = "v3";
|
|
344
|
+
defaultObjectGenerationMode = "json";
|
|
345
|
+
supportsImageUrls = false;
|
|
346
|
+
// The AI SDK matches this map by media type (or '*'), not by URL scheme.
|
|
347
|
+
// Using '*' stops the SDK from downloading URLs itself so the Clark server
|
|
348
|
+
// can fetch them instead.
|
|
349
|
+
supportedUrls = { "*": [/^https:/] };
|
|
350
|
+
modelId;
|
|
351
|
+
config;
|
|
352
|
+
requestBuilder;
|
|
353
|
+
transport;
|
|
354
|
+
constructor(modelId, config, dependencies = {}) {
|
|
355
|
+
this.modelId = modelId;
|
|
356
|
+
this.config = config;
|
|
357
|
+
this.requestBuilder =
|
|
358
|
+
dependencies.requestBuilder ?? new DefaultClarkRequestBuilder();
|
|
359
|
+
this.transport = dependencies.transport ?? new DefaultClarkTransport();
|
|
360
|
+
}
|
|
361
|
+
get provider() {
|
|
362
|
+
return this.config.provider;
|
|
363
|
+
}
|
|
364
|
+
supportsUrl(url) {
|
|
365
|
+
return url.protocol === "https:";
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Start the debug hooks for one stream, copying this call's IDs in so every
|
|
369
|
+
* log line from the stream can be tied back to this run.
|
|
370
|
+
*
|
|
371
|
+
* Everything is wrapped so a sink that throws can't break streaming. (`?.`
|
|
372
|
+
* alone only guards a missing sink, not one that throws.)
|
|
373
|
+
*/
|
|
374
|
+
openTelemetryScope(attributes, headers, fallbackHeaders) {
|
|
375
|
+
const telemetry = this.config.telemetry;
|
|
376
|
+
if (!telemetry) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const scope = telemetry.forStream({
|
|
381
|
+
provider: this.config.provider,
|
|
382
|
+
clarkModel: this.modelId,
|
|
383
|
+
coreModel: attributes?.model,
|
|
384
|
+
sessionId: attributes?.session_id,
|
|
385
|
+
promptId: attributes?.prompt_id,
|
|
386
|
+
applicationId: attributes?.application_id,
|
|
387
|
+
chatMessageId: attributes?.chatmessage_id,
|
|
388
|
+
streamId: headerValue(headers, "x-superblocks-stream-id") ??
|
|
389
|
+
headerValue(fallbackHeaders, "x-superblocks-stream-id"),
|
|
390
|
+
streamAttempt: headerValue(headers, "x-superblocks-stream-attempt") ??
|
|
391
|
+
headerValue(fallbackHeaders, "x-superblocks-stream-attempt"),
|
|
392
|
+
});
|
|
393
|
+
return scope ? guardTelemetryScope(scope) : undefined;
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async doGenerate(options) {
|
|
400
|
+
const { args, warnings } = await this.requestBuilder.build(options, this.modelId);
|
|
401
|
+
const optionsWithMetadata = options;
|
|
402
|
+
const { value } = await this.transport.postJson({
|
|
403
|
+
url: `${this.config.baseURL}/generate`,
|
|
404
|
+
headers: combineHeaders(this.config.headers(), options.headers),
|
|
405
|
+
body: {
|
|
406
|
+
...args,
|
|
407
|
+
attributes: optionsWithMetadata.attributes,
|
|
408
|
+
},
|
|
409
|
+
failedResponseHandler: clarkFailedResponseHandler,
|
|
410
|
+
successfulResponseHandler: clarkJsonResponseHandler,
|
|
411
|
+
abortSignal: options.abortSignal,
|
|
412
|
+
fetch: this.config.fetch,
|
|
413
|
+
});
|
|
414
|
+
const content = [];
|
|
415
|
+
if (value.text != null && value.text !== "") {
|
|
416
|
+
content.push({ type: "text", text: value.text });
|
|
417
|
+
}
|
|
418
|
+
else if (value.object != null) {
|
|
419
|
+
content.push({ type: "text", text: JSON.stringify(value.object) });
|
|
420
|
+
}
|
|
421
|
+
if (Array.isArray(value.files)) {
|
|
422
|
+
for (const file of value.files) {
|
|
423
|
+
content.push({
|
|
424
|
+
type: "file",
|
|
425
|
+
mediaType: file.mimeType ?? "application/octet-stream",
|
|
426
|
+
data: file.base64,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (Array.isArray(value.toolCalls)) {
|
|
431
|
+
for (const toolCall of value.toolCalls) {
|
|
432
|
+
content.push({
|
|
433
|
+
type: "tool-call",
|
|
434
|
+
toolCallId: toolCall.toolCallId,
|
|
435
|
+
toolName: toolCall.toolName,
|
|
436
|
+
input: serializeToolInput(toolCall.args),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (Array.isArray(value.sources)) {
|
|
441
|
+
for (const source of value.sources) {
|
|
442
|
+
const sourceRecord = asRecord(source);
|
|
443
|
+
if (sourceRecord == null) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
content.push({
|
|
447
|
+
...sourceRecord,
|
|
448
|
+
type: "source",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Strip undefined values to satisfy provider metadata JSON shape.
|
|
453
|
+
const sanitizedProviderMetadata = value.providerMetadata
|
|
454
|
+
? JSON.parse(JSON.stringify(value.providerMetadata))
|
|
455
|
+
: undefined;
|
|
456
|
+
return {
|
|
457
|
+
content,
|
|
458
|
+
finishReason: toFinishReason(value.finishReason),
|
|
459
|
+
usage: toUsage(value.usage),
|
|
460
|
+
providerMetadata: sanitizedProviderMetadata,
|
|
461
|
+
response: {
|
|
462
|
+
headers: value.response?.headers,
|
|
463
|
+
},
|
|
464
|
+
request: {
|
|
465
|
+
body: JSON.stringify(args),
|
|
466
|
+
},
|
|
467
|
+
warnings: mergeWarnings(warnings, value.warnings),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async doStream(options) {
|
|
471
|
+
const { args, warnings } = await this.requestBuilder.build(options, this.modelId);
|
|
472
|
+
const optionsWithMetadata = options;
|
|
473
|
+
const headers = combineHeaders(this.config.headers(), options.headers);
|
|
474
|
+
const telemetryScope = this.openTelemetryScope(optionsWithMetadata.attributes, options.headers, headers);
|
|
475
|
+
const body = {
|
|
476
|
+
...args,
|
|
477
|
+
stream: true,
|
|
478
|
+
attributes: optionsWithMetadata.attributes,
|
|
479
|
+
};
|
|
480
|
+
let responseHeaders;
|
|
481
|
+
let response;
|
|
482
|
+
try {
|
|
483
|
+
({ responseHeaders, value: response } = await this.transport.postJson({
|
|
484
|
+
url: `${this.config.baseURL}/stream`,
|
|
485
|
+
headers,
|
|
486
|
+
body,
|
|
487
|
+
failedResponseHandler: clarkFailedResponseHandler,
|
|
488
|
+
successfulResponseHandler: makeClarkStreamResponseHandler(telemetryScope),
|
|
489
|
+
abortSignal: options.abortSignal,
|
|
490
|
+
fetch: this.config.fetch,
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
telemetryScope?.requestFailed?.(requestFailure(error, options.abortSignal?.aborted === true));
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
let finishReason;
|
|
498
|
+
let providerMetadata;
|
|
499
|
+
let usage = toUsage();
|
|
500
|
+
let streamStartEnqueued = false;
|
|
501
|
+
return {
|
|
502
|
+
stream: reportStreamReadFailures(response, telemetryScope, options.abortSignal).pipeThrough(new TransformStream({
|
|
503
|
+
transform(chunk, controller) {
|
|
504
|
+
if (chunk.success) {
|
|
505
|
+
const value = chunk.value;
|
|
506
|
+
if (!streamStartEnqueued &&
|
|
507
|
+
value.type !== "stream-start" &&
|
|
508
|
+
warnings.length > 0) {
|
|
509
|
+
controller.enqueue({
|
|
510
|
+
type: "stream-start",
|
|
511
|
+
warnings,
|
|
512
|
+
});
|
|
513
|
+
streamStartEnqueued = true;
|
|
514
|
+
}
|
|
515
|
+
switch (value.type) {
|
|
516
|
+
case "stream-start": {
|
|
517
|
+
if (streamStartEnqueued) {
|
|
518
|
+
// Only one stream-start is allowed downstream and we already
|
|
519
|
+
// sent it. Later ones still count as activity, so pass them
|
|
520
|
+
// through.
|
|
521
|
+
forwardAsActivity(controller, value);
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
const mergedWarnings = mergeWarnings(warnings, asWarnings(value.warnings));
|
|
525
|
+
if (mergedWarnings.length > 0) {
|
|
526
|
+
controller.enqueue({
|
|
527
|
+
type: "stream-start",
|
|
528
|
+
warnings: mergedWarnings,
|
|
529
|
+
});
|
|
530
|
+
streamStartEnqueued = true;
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// Keep streamStartEnqueued=false so a later stream-start
|
|
534
|
+
// that carries warnings can still surface that metadata.
|
|
535
|
+
forwardAsActivity(controller, value);
|
|
536
|
+
}
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
case "start":
|
|
540
|
+
case "start-step": {
|
|
541
|
+
// AI SDK v5 stream framing: `start` marks the stream begin
|
|
542
|
+
// and `start-step` each step begin. They carry no content,
|
|
543
|
+
// so there is nothing to map — forward as raw activity to
|
|
544
|
+
// keep the client stall timer happy. Handled explicitly (not
|
|
545
|
+
// via `default`) so they are NOT logged as
|
|
546
|
+
// `clark_sse.unknown_chunk`: they are expected framing, not a
|
|
547
|
+
// type we silently dropped and need to investigate.
|
|
548
|
+
forwardAsActivity(controller, value);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case "text-start": {
|
|
552
|
+
const id = asString(value.id);
|
|
553
|
+
if (id == null) {
|
|
554
|
+
forwardAsActivity(controller, value);
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
const providerMetadata = asProviderMetadata(value.providerMetadata);
|
|
558
|
+
controller.enqueue({
|
|
559
|
+
type: "text-start",
|
|
560
|
+
id,
|
|
561
|
+
...(providerMetadata && { providerMetadata }),
|
|
562
|
+
});
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
case "text-delta": {
|
|
566
|
+
const id = asString(value.id);
|
|
567
|
+
const delta = asString(value.text);
|
|
568
|
+
if (id == null || delta == null) {
|
|
569
|
+
forwardAsActivity(controller, value);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
controller.enqueue({
|
|
573
|
+
type: "text-delta",
|
|
574
|
+
id,
|
|
575
|
+
delta,
|
|
576
|
+
});
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "reasoning-start": {
|
|
580
|
+
const id = asString(value.id);
|
|
581
|
+
if (id == null) {
|
|
582
|
+
forwardAsActivity(controller, value);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
controller.enqueue({
|
|
586
|
+
type: "reasoning-start",
|
|
587
|
+
id,
|
|
588
|
+
providerMetadata: asProviderMetadata(value.providerMetadata),
|
|
589
|
+
});
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
case "reasoning-delta": {
|
|
593
|
+
const id = asString(value.id);
|
|
594
|
+
if (id == null) {
|
|
595
|
+
forwardAsActivity(controller, value);
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
controller.enqueue({
|
|
599
|
+
type: "reasoning-delta",
|
|
600
|
+
id,
|
|
601
|
+
delta: asString(value.text) ?? "",
|
|
602
|
+
providerMetadata: asProviderMetadata(value.providerMetadata),
|
|
603
|
+
});
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case "reasoning-end": {
|
|
607
|
+
const id = asString(value.id);
|
|
608
|
+
if (id == null) {
|
|
609
|
+
forwardAsActivity(controller, value);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
controller.enqueue({
|
|
613
|
+
type: "reasoning-end",
|
|
614
|
+
id,
|
|
615
|
+
providerMetadata: asProviderMetadata(value.providerMetadata),
|
|
616
|
+
});
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
case "tool-input-start": {
|
|
620
|
+
const id = asString(value.toolCallId) ?? asString(value.id);
|
|
621
|
+
const toolName = asString(value.toolName);
|
|
622
|
+
if (id == null || toolName == null) {
|
|
623
|
+
forwardAsActivity(controller, value);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
controller.enqueue({
|
|
627
|
+
type: "tool-input-start",
|
|
628
|
+
id,
|
|
629
|
+
toolName,
|
|
630
|
+
});
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
case "tool-input-delta": {
|
|
634
|
+
const id = asString(value.toolCallId) ?? asString(value.id);
|
|
635
|
+
const delta = asString(value.delta);
|
|
636
|
+
if (id == null || delta == null) {
|
|
637
|
+
forwardAsActivity(controller, value);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
controller.enqueue({
|
|
641
|
+
type: "tool-input-delta",
|
|
642
|
+
id,
|
|
643
|
+
delta: unwrapToolInputDelta(delta),
|
|
644
|
+
});
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
case "tool-input-end": {
|
|
648
|
+
const id = asString(value.toolCallId) ?? asString(value.id);
|
|
649
|
+
if (id == null) {
|
|
650
|
+
forwardAsActivity(controller, value);
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
controller.enqueue({
|
|
654
|
+
type: "tool-input-end",
|
|
655
|
+
id,
|
|
656
|
+
});
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
case "tool-call": {
|
|
660
|
+
const toolCallId = asString(value.toolCallId);
|
|
661
|
+
const toolName = asString(value.toolName);
|
|
662
|
+
if (toolCallId == null || toolName == null) {
|
|
663
|
+
forwardAsActivity(controller, value);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
controller.enqueue({
|
|
667
|
+
type: "tool-call",
|
|
668
|
+
toolCallId,
|
|
669
|
+
toolName,
|
|
670
|
+
input: serializeToolInput(value.input),
|
|
671
|
+
});
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
case "file": {
|
|
675
|
+
const data = asString(value.base64);
|
|
676
|
+
if (data == null) {
|
|
677
|
+
forwardAsActivity(controller, value);
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
controller.enqueue({
|
|
681
|
+
type: "file",
|
|
682
|
+
mediaType: asString(value.mimeType) ?? "application/octet-stream",
|
|
683
|
+
data,
|
|
684
|
+
});
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
case "source": {
|
|
688
|
+
const source = asRecord(value.source);
|
|
689
|
+
if (source == null) {
|
|
690
|
+
forwardAsActivity(controller, value);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
controller.enqueue({
|
|
694
|
+
type: "source",
|
|
695
|
+
...source,
|
|
696
|
+
});
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "text-end": {
|
|
700
|
+
const id = asString(value.id);
|
|
701
|
+
if (id == null) {
|
|
702
|
+
forwardAsActivity(controller, value);
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
controller.enqueue({
|
|
706
|
+
type: "text-end",
|
|
707
|
+
id,
|
|
708
|
+
});
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
// Finish reason and usage are replaced each time, not added up.
|
|
712
|
+
// The final "finish" total wins. Compaction provider metadata
|
|
713
|
+
// can arrive on "finish-step" first, so retain the latest
|
|
714
|
+
// non-empty value for the synthesized finish event.
|
|
715
|
+
case "finish":
|
|
716
|
+
case "finish-step": {
|
|
717
|
+
finishReason = asString(value.finishReason);
|
|
718
|
+
const usageData = asUsage(value.usage ?? value.totalUsage);
|
|
719
|
+
usage = toUsage(usageData);
|
|
720
|
+
const nextProviderMetadata = asProviderMetadata(value.providerMetadata);
|
|
721
|
+
if (nextProviderMetadata != null &&
|
|
722
|
+
Object.keys(nextProviderMetadata).length > 0) {
|
|
723
|
+
providerMetadata = nextProviderMetadata;
|
|
724
|
+
}
|
|
725
|
+
if (value.type === "finish-step") {
|
|
726
|
+
forwardAsActivity(controller, value);
|
|
727
|
+
}
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
case "error": {
|
|
731
|
+
// Record that an error message came through, and whether it
|
|
732
|
+
// actually carried an error payload, so a malformed error can
|
|
733
|
+
// be told apart from a normal one.
|
|
734
|
+
telemetryScope?.errorChunk({ hasError: value.error != null });
|
|
735
|
+
controller.enqueue({
|
|
736
|
+
type: "error",
|
|
737
|
+
error: value.error,
|
|
738
|
+
});
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
case "raw": {
|
|
742
|
+
// Keep-alive "still here" pings, sent while the model is
|
|
743
|
+
// thinking and producing nothing. These are the AI proxy's own
|
|
744
|
+
// 30s heartbeat (the proxy relay no longer asks the provider
|
|
745
|
+
// SDK for raw chunks, so provider pings do not reach here).
|
|
746
|
+
// They carry no content; forwarding them stops the client's
|
|
747
|
+
// stall timer from firing by mistake. Recording rawParsed
|
|
748
|
+
// proves these reached this step: if the server sent pings but
|
|
749
|
+
// none show up here, the data was lost between the server and
|
|
750
|
+
// Clark.
|
|
751
|
+
telemetryScope?.rawParsed();
|
|
752
|
+
controller.enqueue({
|
|
753
|
+
type: "raw",
|
|
754
|
+
rawValue: value.rawValue ?? value,
|
|
755
|
+
});
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
case "object": {
|
|
759
|
+
const id = asString(value.id);
|
|
760
|
+
if (id == null) {
|
|
761
|
+
forwardAsActivity(controller, value);
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
controller.enqueue({
|
|
765
|
+
type: "text-delta",
|
|
766
|
+
id,
|
|
767
|
+
delta: JSON.stringify(value.object),
|
|
768
|
+
});
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
case "tool-result": {
|
|
772
|
+
// this might not work properly if we have server side tool results, we'll probably need to filter out specifically ones that are expected to run client side
|
|
773
|
+
// right now we keep the empty server result out of content, but still surface it as activity.
|
|
774
|
+
forwardAsActivity(controller, value);
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
default: {
|
|
778
|
+
// A message type we don't handle: pass it through as raw so
|
|
779
|
+
// the client's stall timer stays happy, and log it so a type
|
|
780
|
+
// we silently dropped is still visible.
|
|
781
|
+
telemetryScope?.unknownChunk(value.type);
|
|
782
|
+
forwardAsActivity(controller, value);
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
controller.enqueue({ type: "error", error: chunk.error });
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
flush(controller) {
|
|
792
|
+
if (!streamStartEnqueued && warnings.length > 0) {
|
|
793
|
+
controller.enqueue({
|
|
794
|
+
type: "stream-start",
|
|
795
|
+
warnings,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
controller.enqueue({
|
|
799
|
+
type: "finish",
|
|
800
|
+
finishReason: toFinishReason(finishReason),
|
|
801
|
+
usage,
|
|
802
|
+
...(providerMetadata && { providerMetadata }),
|
|
803
|
+
});
|
|
804
|
+
},
|
|
805
|
+
})),
|
|
806
|
+
response: { headers: responseHeaders },
|
|
807
|
+
request: { body: JSON.stringify(body) },
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const clarkJsonResponseHandler = async ({ response }) => {
|
|
812
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
813
|
+
const body = (await response.json());
|
|
814
|
+
return {
|
|
815
|
+
responseHeaders,
|
|
816
|
+
value: body,
|
|
817
|
+
rawValue: body,
|
|
818
|
+
};
|
|
819
|
+
};
|
|
820
|
+
function makeClarkStreamResponseHandler(telemetryScope) {
|
|
821
|
+
return async ({ response }) => {
|
|
822
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
823
|
+
if (response.body == null) {
|
|
824
|
+
throw new EmptyResponseBodyError({});
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
responseHeaders,
|
|
828
|
+
value: response.body
|
|
829
|
+
.pipeThrough(new TextDecoderStream())
|
|
830
|
+
.pipeThrough(new EventSourceParserStream())
|
|
831
|
+
.pipeThrough(new TransformStream({
|
|
832
|
+
async transform({ data }, controller) {
|
|
833
|
+
try {
|
|
834
|
+
const json = JSON.parse(data);
|
|
835
|
+
controller.enqueue({
|
|
836
|
+
success: true,
|
|
837
|
+
value: json,
|
|
838
|
+
rawValue: json,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
// A streamed line we couldn't parse as JSON. Log the failure
|
|
843
|
+
// (length only, never the data itself) so a parse problem here
|
|
844
|
+
// is visible instead of quietly turning into an error later.
|
|
845
|
+
telemetryScope?.parseError(data.length);
|
|
846
|
+
controller.enqueue({
|
|
847
|
+
success: false,
|
|
848
|
+
error: JSONParseError.isInstance(error)
|
|
849
|
+
? error
|
|
850
|
+
: new JSONParseError({
|
|
851
|
+
text: data,
|
|
852
|
+
cause: error,
|
|
853
|
+
}),
|
|
854
|
+
rawValue: data,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
})),
|
|
859
|
+
};
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
//# sourceMappingURL=clark-language-model.js.map
|