llm-stream-assemble 1.0.1 → 1.2.0
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/README.md +209 -58
- package/dist/adapters/gemini.cjs +374 -0
- package/dist/adapters/gemini.cjs.map +1 -0
- package/dist/adapters/gemini.d.cts +9 -0
- package/dist/adapters/gemini.d.ts +9 -0
- package/dist/adapters/gemini.js +372 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/openai-chat.cjs +3 -0
- package/dist/adapters/openai-chat.cjs.map +1 -1
- package/dist/adapters/openai-chat.js +3 -0
- package/dist/adapters/openai-chat.js.map +1 -1
- package/dist/adapters/openai-compatible.cjs +18 -3
- package/dist/adapters/openai-compatible.cjs.map +1 -1
- package/dist/adapters/openai-compatible.d.cts +1 -1
- package/dist/adapters/openai-compatible.d.ts +1 -1
- package/dist/adapters/openai-compatible.js +18 -3
- package/dist/adapters/openai-compatible.js.map +1 -1
- package/dist/index.cjs +325 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +325 -4
- package/dist/index.js.map +1 -1
- package/package.json +19 -3
package/dist/index.cjs
CHANGED
|
@@ -1051,6 +1051,9 @@ var OpenAIChatLikeParser = class {
|
|
|
1051
1051
|
}
|
|
1052
1052
|
const looseError = providerErrorPayload(payload, this.options);
|
|
1053
1053
|
if (looseError) return openAIProviderErrorChunks(looseError, false, this.options);
|
|
1054
|
+
if (!this.options.looseErrorShape && asString(payload.error) && !isRecord(payload.error)) {
|
|
1055
|
+
return [];
|
|
1056
|
+
}
|
|
1054
1057
|
if (this.options.rejectUnrecognizedPayloads && !this.isRecognizableChunk(payload)) {
|
|
1055
1058
|
throwUnrecognizedChunkError(this.options);
|
|
1056
1059
|
}
|
|
@@ -1313,18 +1316,30 @@ var DEFAULT_PRESET = {
|
|
|
1313
1316
|
reasoningFieldAliases: ["thinking", "thinking_content"]
|
|
1314
1317
|
};
|
|
1315
1318
|
var PRESET_OVERRIDES = {
|
|
1319
|
+
deepseek: { reasoningFieldAliases: ["reasoning_content", "reasoning", "thinking"] },
|
|
1316
1320
|
openrouter: { reasoningFieldAliases: ["reasoning"] },
|
|
1317
|
-
together: { reasoningFieldAliases: ["reasoning", "reasoning_delta"] }
|
|
1321
|
+
together: { reasoningFieldAliases: ["reasoning", "reasoning_delta"] },
|
|
1322
|
+
azure: {
|
|
1323
|
+
looseErrorShape: false,
|
|
1324
|
+
allowMissingMetadata: false,
|
|
1325
|
+
useChoicePositionFallback: true,
|
|
1326
|
+
reasoningFieldAliases: []
|
|
1327
|
+
}
|
|
1318
1328
|
};
|
|
1319
1329
|
function openaiCompatibleAdapter(options = {}) {
|
|
1320
1330
|
const preset = providerPreset(options.provider ?? "generic");
|
|
1331
|
+
const resolvedAllowMissingMetadata = options.allowMissingMetadata ?? preset.allowMissingMetadata ?? DEFAULT_PRESET.allowMissingMetadata;
|
|
1332
|
+
const resolvedLooseErrorShape = options.looseErrorShape ?? preset.looseErrorShape ?? DEFAULT_PRESET.looseErrorShape;
|
|
1333
|
+
const resolvedUseChoicePositionFallback = options.useChoicePositionFallback ?? preset.useChoicePositionFallback ?? DEFAULT_PRESET.useChoicePositionFallback;
|
|
1321
1334
|
return createOpenAIChatLikeAdapter({
|
|
1322
|
-
...preset,
|
|
1323
1335
|
...options,
|
|
1336
|
+
looseErrorShape: resolvedLooseErrorShape,
|
|
1337
|
+
allowMissingMetadata: resolvedAllowMissingMetadata,
|
|
1338
|
+
useChoicePositionFallback: resolvedUseChoicePositionFallback,
|
|
1324
1339
|
errorPrefix: "openaiCompatibleAdapter",
|
|
1325
1340
|
usageInputTokenFields: ["prompt_tokens", "input_tokens"],
|
|
1326
1341
|
usageOutputTokenFields: ["completion_tokens", "output_tokens"],
|
|
1327
|
-
rejectUnrecognizedPayloads:
|
|
1342
|
+
rejectUnrecognizedPayloads: resolvedAllowMissingMetadata === false,
|
|
1328
1343
|
reasoningFieldAliases: [
|
|
1329
1344
|
...preset.reasoningFieldAliases ?? [],
|
|
1330
1345
|
...options.reasoningFieldAliases ?? []
|
|
@@ -1976,6 +1991,312 @@ function toolId(payload, item) {
|
|
|
1976
1991
|
return callId ?? itemId ?? `response_tool:${index ?? 0}`;
|
|
1977
1992
|
}
|
|
1978
1993
|
|
|
1994
|
+
// src/adapters/gemini.ts
|
|
1995
|
+
function geminiAdapter(options = {}) {
|
|
1996
|
+
const parser = new GeminiStreamParser(options);
|
|
1997
|
+
return createStreamAdapter({
|
|
1998
|
+
parser,
|
|
1999
|
+
parseResponse: parseResponse4,
|
|
2000
|
+
options
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
var GeminiStreamParser = class {
|
|
2004
|
+
constructor(options) {
|
|
2005
|
+
this.options = options;
|
|
2006
|
+
}
|
|
2007
|
+
options;
|
|
2008
|
+
metadataEmitted = false;
|
|
2009
|
+
tools = /* @__PURE__ */ new Map();
|
|
2010
|
+
openToolByChoice = /* @__PURE__ */ new Map();
|
|
2011
|
+
toolCounter = 0;
|
|
2012
|
+
parseChunk(raw) {
|
|
2013
|
+
const trimmed = raw.trim();
|
|
2014
|
+
if (trimmed.length === 0 || trimmed === "[DONE]") return [];
|
|
2015
|
+
const payload = parseAdapterJSON(trimmed, "geminiAdapter.parseChunk");
|
|
2016
|
+
if (!isRecord(payload)) {
|
|
2017
|
+
throw libraryError("geminiAdapter.parseChunk expected a JSON object");
|
|
2018
|
+
}
|
|
2019
|
+
if (isRecord(payload.error)) {
|
|
2020
|
+
return providerErrorChunksFromPayload(
|
|
2021
|
+
payload.error,
|
|
2022
|
+
"geminiAdapter.parseChunk",
|
|
2023
|
+
false,
|
|
2024
|
+
"Gemini provider error"
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
const chunks = [];
|
|
2028
|
+
const feedback = isRecord(payload.promptFeedback) ? payload.promptFeedback : void 0;
|
|
2029
|
+
const blockReason = feedback ? asString(feedback.blockReason) : void 0;
|
|
2030
|
+
if (blockReason) {
|
|
2031
|
+
return providerErrorChunksFromMessage(`Gemini prompt blocked: ${blockReason}`, false);
|
|
2032
|
+
}
|
|
2033
|
+
chunks.push(...this.metadataChunks(payload));
|
|
2034
|
+
const usage = usageChunk3(payload.usageMetadata);
|
|
2035
|
+
if (usage) chunks.push(usage);
|
|
2036
|
+
const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
|
|
2037
|
+
for (const candidate of candidates) {
|
|
2038
|
+
if (!isRecord(candidate)) continue;
|
|
2039
|
+
chunks.push(...this.candidateChunks(candidate));
|
|
2040
|
+
}
|
|
2041
|
+
return chunks;
|
|
2042
|
+
}
|
|
2043
|
+
metadataChunks(payload) {
|
|
2044
|
+
if (this.metadataEmitted) return [];
|
|
2045
|
+
const responseId = asString(payload.responseId);
|
|
2046
|
+
const model = asString(payload.modelVersion);
|
|
2047
|
+
if (!responseId && !model) return [];
|
|
2048
|
+
this.metadataEmitted = true;
|
|
2049
|
+
const chunks = [];
|
|
2050
|
+
if (responseId) chunks.push({ kind: "message-start", id: responseId });
|
|
2051
|
+
chunks.push(
|
|
2052
|
+
optionalRawChunk({
|
|
2053
|
+
kind: "metadata",
|
|
2054
|
+
responseId,
|
|
2055
|
+
model,
|
|
2056
|
+
raw: { responseId, modelVersion: model }
|
|
2057
|
+
})
|
|
2058
|
+
);
|
|
2059
|
+
return chunks;
|
|
2060
|
+
}
|
|
2061
|
+
candidateChunks(candidate) {
|
|
2062
|
+
const chunks = [];
|
|
2063
|
+
const choiceIndex = asNumber(candidate.index) ?? 0;
|
|
2064
|
+
const citation = candidate.citationMetadata;
|
|
2065
|
+
const grounding = candidate.groundingMetadata;
|
|
2066
|
+
if (citation !== void 0 || grounding !== void 0) {
|
|
2067
|
+
chunks.push(
|
|
2068
|
+
optionalRawChunk({
|
|
2069
|
+
kind: "metadata",
|
|
2070
|
+
raw: { citationMetadata: citation, groundingMetadata: grounding }
|
|
2071
|
+
})
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const content = isRecord(candidate.content) ? candidate.content : void 0;
|
|
2075
|
+
const parts = content && Array.isArray(content.parts) ? content.parts : [];
|
|
2076
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex += 1) {
|
|
2077
|
+
const part = parts[partIndex];
|
|
2078
|
+
if (!isRecord(part)) continue;
|
|
2079
|
+
chunks.push(...this.partChunks(part, partIndex, choiceIndex));
|
|
2080
|
+
}
|
|
2081
|
+
const finishReason2 = asString(candidate.finishReason);
|
|
2082
|
+
if (finishReason2) {
|
|
2083
|
+
const mapped = mapFinishReason(finishReason2);
|
|
2084
|
+
if (mapped === "error") {
|
|
2085
|
+
chunks.push(
|
|
2086
|
+
...providerErrorChunksFromMessage(`Gemini finishReason: ${finishReason2}`, false)
|
|
2087
|
+
);
|
|
2088
|
+
} else {
|
|
2089
|
+
chunks.push({ kind: "finish", reason: mapped, choiceIndex });
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return chunks;
|
|
2093
|
+
}
|
|
2094
|
+
partChunks(part, partIndex, choiceIndex) {
|
|
2095
|
+
if (isRecord(part.functionResponse)) return [];
|
|
2096
|
+
if (part.inlineData !== void 0 || part.fileData !== void 0) return [];
|
|
2097
|
+
if (part.executableCode !== void 0 || part.codeExecutionResult !== void 0) return [];
|
|
2098
|
+
const thought = part.thought === true;
|
|
2099
|
+
const text = asString(part.text);
|
|
2100
|
+
if (thought && text) {
|
|
2101
|
+
return [{ kind: "reasoning-delta", text, variant: "detail" }];
|
|
2102
|
+
}
|
|
2103
|
+
if (thought && !text) return [];
|
|
2104
|
+
if (text !== void 0) {
|
|
2105
|
+
if (text.length === 0) return [];
|
|
2106
|
+
if (this.options.jsonMode) return [{ kind: "json-delta", delta: text }];
|
|
2107
|
+
return [{ kind: "text-delta", text, choiceIndex }];
|
|
2108
|
+
}
|
|
2109
|
+
const functionCall = isRecord(part.functionCall) ? part.functionCall : void 0;
|
|
2110
|
+
if (functionCall) return this.functionCallChunks(functionCall, partIndex, choiceIndex);
|
|
2111
|
+
if (Object.keys(part).length === 0) return [];
|
|
2112
|
+
return [];
|
|
2113
|
+
}
|
|
2114
|
+
functionCallChunks(functionCall, partIndex, choiceIndex) {
|
|
2115
|
+
const chunks = [];
|
|
2116
|
+
const explicitId = asString(functionCall.id);
|
|
2117
|
+
const name = asString(functionCall.name);
|
|
2118
|
+
let toolKey2 = this.resolveToolKey(explicitId, partIndex, choiceIndex, name);
|
|
2119
|
+
if (name && !this.tools.has(toolKey2)) {
|
|
2120
|
+
toolKey2 = explicitId ?? `${choiceIndex}:${partIndex}`;
|
|
2121
|
+
const id = explicitId ?? `gemini:${choiceIndex}:${this.toolCounter++}`;
|
|
2122
|
+
this.tools.set(toolKey2, {
|
|
2123
|
+
id,
|
|
2124
|
+
name,
|
|
2125
|
+
index: partIndex,
|
|
2126
|
+
choiceIndex,
|
|
2127
|
+
lastArgsJson: "",
|
|
2128
|
+
open: true
|
|
2129
|
+
});
|
|
2130
|
+
this.openToolByChoice.set(choiceIndex, toolKey2);
|
|
2131
|
+
chunks.push(
|
|
2132
|
+
optionalRawChunk({
|
|
2133
|
+
kind: "tool-start",
|
|
2134
|
+
id,
|
|
2135
|
+
name,
|
|
2136
|
+
index: partIndex,
|
|
2137
|
+
choiceIndex
|
|
2138
|
+
})
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
const current = this.tools.get(toolKey2);
|
|
2142
|
+
if (!current) return chunks;
|
|
2143
|
+
const partialArgs = Array.isArray(functionCall.partialArgs) ? functionCall.partialArgs : [];
|
|
2144
|
+
for (const item of partialArgs) {
|
|
2145
|
+
if (!isRecord(item)) continue;
|
|
2146
|
+
const delta = partialArgDelta(item);
|
|
2147
|
+
if (delta) {
|
|
2148
|
+
chunks.push(
|
|
2149
|
+
optionalRawChunk({
|
|
2150
|
+
kind: "tool-args-delta",
|
|
2151
|
+
id: current.id,
|
|
2152
|
+
delta,
|
|
2153
|
+
index: current.index,
|
|
2154
|
+
choiceIndex
|
|
2155
|
+
})
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (isRecord(functionCall.args)) {
|
|
2160
|
+
const delta = this.argsObjectDelta(toolKey2, functionCall.args);
|
|
2161
|
+
if (delta) {
|
|
2162
|
+
chunks.push(
|
|
2163
|
+
optionalRawChunk({
|
|
2164
|
+
kind: "tool-args-delta",
|
|
2165
|
+
id: current.id,
|
|
2166
|
+
delta,
|
|
2167
|
+
index: current.index,
|
|
2168
|
+
choiceIndex
|
|
2169
|
+
})
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
const willContinue = functionCall.willContinue === true;
|
|
2174
|
+
const hasPartialArgs = partialArgs.length > 0;
|
|
2175
|
+
const hasArgs = isRecord(functionCall.args) && Object.keys(functionCall.args).length > 0;
|
|
2176
|
+
if (!willContinue && !hasPartialArgs && (hasArgs || name && isRecord(functionCall.args))) {
|
|
2177
|
+
chunks.push(
|
|
2178
|
+
optionalRawChunk({
|
|
2179
|
+
kind: "tool-done",
|
|
2180
|
+
id: current.id,
|
|
2181
|
+
index: current.index,
|
|
2182
|
+
choiceIndex
|
|
2183
|
+
})
|
|
2184
|
+
);
|
|
2185
|
+
current.open = false;
|
|
2186
|
+
} else if (!willContinue && hasPartialArgs && partialArgs.every((item) => isRecord(item) && item.willContinue !== true)) {
|
|
2187
|
+
if (current.open) {
|
|
2188
|
+
chunks.push(
|
|
2189
|
+
optionalRawChunk({
|
|
2190
|
+
kind: "tool-done",
|
|
2191
|
+
id: current.id,
|
|
2192
|
+
index: current.index,
|
|
2193
|
+
choiceIndex
|
|
2194
|
+
})
|
|
2195
|
+
);
|
|
2196
|
+
current.open = false;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return chunks;
|
|
2200
|
+
}
|
|
2201
|
+
resolveToolKey(explicitId, partIndex, choiceIndex, name) {
|
|
2202
|
+
if (explicitId) return explicitId;
|
|
2203
|
+
const openKey = this.openToolByChoice.get(choiceIndex);
|
|
2204
|
+
if (openKey && this.tools.get(openKey)?.open) return openKey;
|
|
2205
|
+
if (name) {
|
|
2206
|
+
for (const [key, state] of this.tools) {
|
|
2207
|
+
if (state.choiceIndex === choiceIndex && state.name === name && state.open) return key;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return `${choiceIndex}:${partIndex}`;
|
|
2211
|
+
}
|
|
2212
|
+
argsObjectDelta(toolKey2, args) {
|
|
2213
|
+
const next = JSON.stringify(args);
|
|
2214
|
+
const state = this.tools.get(toolKey2);
|
|
2215
|
+
const prev = state?.lastArgsJson ?? "";
|
|
2216
|
+
if (next === prev) return void 0;
|
|
2217
|
+
let delta;
|
|
2218
|
+
if (prev.length > 0 && next.startsWith(prev)) {
|
|
2219
|
+
delta = next.slice(prev.length);
|
|
2220
|
+
} else {
|
|
2221
|
+
delta = next;
|
|
2222
|
+
}
|
|
2223
|
+
if (state) state.lastArgsJson = next;
|
|
2224
|
+
return delta.length > 0 ? delta : void 0;
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
function parseResponse4(body, options) {
|
|
2228
|
+
if (!isRecord(body)) {
|
|
2229
|
+
throw libraryError("geminiAdapter.parseResponse expected a GenerateContentResponse object");
|
|
2230
|
+
}
|
|
2231
|
+
const parser = new GeminiStreamParser(options);
|
|
2232
|
+
const chunks = [];
|
|
2233
|
+
if (isRecord(body.error)) {
|
|
2234
|
+
return providerErrorChunksFromPayload(
|
|
2235
|
+
body.error,
|
|
2236
|
+
"geminiAdapter.parseResponse",
|
|
2237
|
+
false,
|
|
2238
|
+
"Gemini provider error"
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
const feedback = isRecord(body.promptFeedback) ? body.promptFeedback : void 0;
|
|
2242
|
+
const blockReason = feedback ? asString(feedback.blockReason) : void 0;
|
|
2243
|
+
if (blockReason) {
|
|
2244
|
+
return providerErrorChunksFromMessage(`Gemini prompt blocked: ${blockReason}`, false);
|
|
2245
|
+
}
|
|
2246
|
+
chunks.push(...parser.parseChunk(JSON.stringify(body)));
|
|
2247
|
+
const hasFinish = chunks.some((chunk) => chunk.kind === "finish");
|
|
2248
|
+
if (!hasFinish) {
|
|
2249
|
+
chunks.push({ kind: "finish", reason: "stop" });
|
|
2250
|
+
}
|
|
2251
|
+
return chunks;
|
|
2252
|
+
}
|
|
2253
|
+
function partialArgDelta(arg) {
|
|
2254
|
+
const stringValue = asString(arg.stringValue);
|
|
2255
|
+
if (stringValue !== void 0) return stringValue;
|
|
2256
|
+
const jsonPath = asString(arg.jsonPath) ?? "$";
|
|
2257
|
+
const key = jsonPath.replace(/^\$\.?/, "").split(".")[0] ?? "value";
|
|
2258
|
+
if (arg.numberValue !== void 0) return JSON.stringify({ [key]: arg.numberValue });
|
|
2259
|
+
if (arg.boolValue !== void 0) return JSON.stringify({ [key]: arg.boolValue });
|
|
2260
|
+
if (arg.nullValue !== void 0) return JSON.stringify({ [key]: null });
|
|
2261
|
+
return void 0;
|
|
2262
|
+
}
|
|
2263
|
+
function mapFinishReason(value) {
|
|
2264
|
+
switch (value) {
|
|
2265
|
+
case "STOP":
|
|
2266
|
+
case "STOP_REASON_UNSPECIFIED":
|
|
2267
|
+
return "stop";
|
|
2268
|
+
case "MAX_TOKENS":
|
|
2269
|
+
return "length";
|
|
2270
|
+
case "SAFETY":
|
|
2271
|
+
case "RECITATION":
|
|
2272
|
+
case "BLOCKLIST":
|
|
2273
|
+
case "PROHIBITED_CONTENT":
|
|
2274
|
+
case "SPII":
|
|
2275
|
+
case "LANGUAGE":
|
|
2276
|
+
return "content_filter";
|
|
2277
|
+
case "MALFORMED_FUNCTION_CALL":
|
|
2278
|
+
return "error";
|
|
2279
|
+
default:
|
|
2280
|
+
return "error";
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
function usageChunk3(value) {
|
|
2284
|
+
if (!isRecord(value)) return void 0;
|
|
2285
|
+
const inputTokens = asNumber(value.promptTokenCount);
|
|
2286
|
+
const outputTokens = asNumber(value.candidatesTokenCount);
|
|
2287
|
+
const reasoningTokens = asNumber(value.thoughtsTokenCount);
|
|
2288
|
+
if (inputTokens === void 0 && outputTokens === void 0 && reasoningTokens === void 0) {
|
|
2289
|
+
return void 0;
|
|
2290
|
+
}
|
|
2291
|
+
return optionalRawChunk({
|
|
2292
|
+
kind: "usage",
|
|
2293
|
+
inputTokens,
|
|
2294
|
+
outputTokens,
|
|
2295
|
+
reasoningTokens,
|
|
2296
|
+
raw: value
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
|
|
1979
2300
|
// src/transforms/collect-stream.ts
|
|
1980
2301
|
async function collectStream(events) {
|
|
1981
2302
|
const result = {
|
|
@@ -2210,6 +2531,7 @@ exports.assembleResponse = assembleResponse;
|
|
|
2210
2531
|
exports.assembleStream = assembleStream;
|
|
2211
2532
|
exports.collectStream = collectStream;
|
|
2212
2533
|
exports.createAssemblyTransform = createAssemblyTransform;
|
|
2534
|
+
exports.geminiAdapter = geminiAdapter;
|
|
2213
2535
|
exports.isError = isError;
|
|
2214
2536
|
exports.isFinish = isFinish;
|
|
2215
2537
|
exports.isJsonDelta = isJsonDelta;
|