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