llm-stream-assemble 1.0.0 → 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/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: options.allowMissingMetadata === false,
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;