n8n-nodes-coding-stream 0.1.5 → 0.1.7

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.
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CodingStreamChatModel = void 0;
4
+ const N8nModelTracing_1 = require("./N8nModelTracing");
5
+ const reasoningFallback_1 = require("./reasoningFallback");
4
6
  const openai_1 = require("@langchain/openai");
5
- const ai_utilities_1 = require("@n8n/ai-utilities");
6
7
  const n8n_workflow_1 = require("n8n-workflow");
7
8
  class CodingStreamChatModel {
8
9
  constructor() {
@@ -23,10 +24,10 @@ class CodingStreamChatModel {
23
24
  name: 'baseURL',
24
25
  type: 'string',
25
26
  required: true,
26
- default: 'https://external-ai-gateway.sovcombank.group/llm/v1',
27
+ default: '',
27
28
  description: 'Base URL that ends in /v1 (the SDK adds /chat/completions)',
28
29
  },
29
- { displayName: 'Model', name: 'model', type: 'string', required: true, default: 'coding-ultima' },
30
+ { displayName: 'Model', name: 'model', type: 'string', required: true, default: '' },
30
31
  {
31
32
  displayName: 'Bearer Token',
32
33
  name: 'bearerToken',
@@ -105,6 +106,7 @@ class CodingStreamChatModel {
105
106
  configuration: {
106
107
  baseURL,
107
108
  defaultHeaders: { 'X-Request-By': requestBy },
109
+ fetch: (0, reasoningFallback_1.createReasoningFallbackFetch)(),
108
110
  },
109
111
  temperature: options.temperature ?? 0.3,
110
112
  ...(options.maxTokens != null ? { maxTokens: Number(options.maxTokens) } : {}),
@@ -114,7 +116,7 @@ class CodingStreamChatModel {
114
116
  timeout: options.timeout ?? 120000,
115
117
  maxRetries: 0,
116
118
  supportsStrictToolCalling: false,
117
- callbacks: [new ai_utilities_1.N8nLlmTracing(this)],
119
+ callbacks: [new N8nModelTracing_1.N8nModelTracing(this)],
118
120
  });
119
121
  return { response: model };
120
122
  }
@@ -0,0 +1,17 @@
1
+ import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
2
+ import type { BaseMessage } from '@langchain/core/messages';
3
+ import type { LLMResult } from '@langchain/core/outputs';
4
+ import { type ISupplyDataFunctions } from 'n8n-workflow';
5
+ export declare class N8nModelTracing extends BaseCallbackHandler {
6
+ private context;
7
+ name: string;
8
+ awaitHandlers: boolean;
9
+ private runs;
10
+ private connection;
11
+ constructor(context: ISupplyDataFunctions);
12
+ private recordInput;
13
+ handleChatModelStart(_model: unknown, messages: BaseMessage[][], runId: string): void;
14
+ handleLLMStart(_model: unknown, prompts: string[], runId: string): void;
15
+ handleLLMEnd(output: LLMResult, runId: string): void;
16
+ handleLLMError(error: Error, runId: string): void;
17
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.N8nModelTracing = void 0;
4
+ const base_1 = require("@langchain/core/callbacks/base");
5
+ const n8n_workflow_1 = require("n8n-workflow");
6
+ class N8nModelTracing extends base_1.BaseCallbackHandler {
7
+ constructor(context) {
8
+ super();
9
+ this.context = context;
10
+ this.name = 'N8nModelTracing';
11
+ this.awaitHandlers = true;
12
+ this.runs = new Map();
13
+ this.connection = n8n_workflow_1.NodeConnectionTypes.AiLanguageModel;
14
+ }
15
+ recordInput(runId, messages) {
16
+ const { index } = this.context.addInputData(this.connection, [[{
17
+ json: {
18
+ messages: JSON.parse(JSON.stringify(messages)),
19
+ },
20
+ }]]);
21
+ this.runs.set(runId, index);
22
+ }
23
+ handleChatModelStart(_model, messages, runId) {
24
+ this.recordInput(runId, messages);
25
+ }
26
+ handleLLMStart(_model, prompts, runId) {
27
+ this.recordInput(runId, prompts);
28
+ }
29
+ handleLLMEnd(output, runId) {
30
+ const index = this.runs.get(runId);
31
+ if (index === undefined)
32
+ return;
33
+ const generations = output.generations.map(group => group.map(generation => ({
34
+ text: generation.text,
35
+ message: 'message' in generation
36
+ ? JSON.parse(JSON.stringify(generation.message))
37
+ : undefined,
38
+ generationInfo: generation.generationInfo,
39
+ })));
40
+ const usage = output.llmOutput?.tokenUsage
41
+ ?? output.generations[0]?.[0]?.message?.usage_metadata;
42
+ this.context.addOutputData(this.connection, index, [[{
43
+ json: {
44
+ response: { generations },
45
+ ...(usage ? { tokenUsage: usage } : {}),
46
+ },
47
+ }]]);
48
+ this.runs.delete(runId);
49
+ }
50
+ handleLLMError(error, runId) {
51
+ const index = this.runs.get(runId);
52
+ if (index === undefined)
53
+ return;
54
+ this.context.addOutputData(this.connection, index, new n8n_workflow_1.NodeOperationError(this.context.getNode(), error));
55
+ this.runs.delete(runId);
56
+ }
57
+ }
58
+ exports.N8nModelTracing = N8nModelTracing;
@@ -0,0 +1 @@
1
+ export declare function createReasoningFallbackFetch(): typeof fetch;
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createReasoningFallbackFetch = createReasoningFallbackFetch;
4
+ const MARKER = ' response';
5
+ function extractAnswer(reasoning) {
6
+ const position = reasoning.indexOf(MARKER);
7
+ if (position === -1)
8
+ return '';
9
+ return reasoning.slice(position + MARKER.length).trimStart();
10
+ }
11
+ function hasContent(content) {
12
+ if (typeof content === 'string')
13
+ return content.length > 0;
14
+ if (Array.isArray(content))
15
+ return content.length > 0;
16
+ return content != null;
17
+ }
18
+ function hasToolCalls(message) {
19
+ return Boolean((Array.isArray(message.tool_calls) && message.tool_calls.length) ||
20
+ message.function_call);
21
+ }
22
+ function rewriteCompletion(data) {
23
+ for (const choice of data.choices ?? []) {
24
+ const message = choice.message;
25
+ if (!message || hasContent(message.content))
26
+ continue;
27
+ if (choice.finish_reason === 'tool_calls')
28
+ continue;
29
+ if (hasToolCalls(message))
30
+ continue;
31
+ const reasoning = message.reasoning_content;
32
+ if (typeof reasoning !== 'string')
33
+ continue;
34
+ const answer = extractAnswer(reasoning);
35
+ if (answer)
36
+ message.content = answer;
37
+ }
38
+ return data;
39
+ }
40
+ function rewriteEventStream(body) {
41
+ const decoder = new TextDecoder();
42
+ const encoder = new TextEncoder();
43
+ const states = new Map();
44
+ let buffer = '';
45
+ const getState = (index) => {
46
+ let state = states.get(index);
47
+ if (!state) {
48
+ state = {
49
+ reasoning: '',
50
+ contentSeen: false,
51
+ toolsSeen: false,
52
+ finished: false,
53
+ };
54
+ states.set(index, state);
55
+ }
56
+ return state;
57
+ };
58
+ return body.pipeThrough(new TransformStream({
59
+ transform(chunk, controller) {
60
+ buffer += decoder.decode(chunk, { stream: true });
61
+ buffer = buffer.replace(/\r\n/g, '\n');
62
+ const send = (value) => {
63
+ controller.enqueue(encoder.encode(value));
64
+ };
65
+ const sendFallback = (index, state) => {
66
+ if (state.finished)
67
+ return;
68
+ state.finished = true;
69
+ if (state.contentSeen || state.toolsSeen || !state.template)
70
+ return;
71
+ const answer = extractAnswer(state.reasoning);
72
+ if (!answer)
73
+ return;
74
+ const fallback = {
75
+ ...state.template,
76
+ choices: [{
77
+ index,
78
+ delta: { content: answer },
79
+ finish_reason: null,
80
+ }],
81
+ };
82
+ send(`data: ${JSON.stringify(fallback)}\n\n`);
83
+ };
84
+ let separator;
85
+ while ((separator = buffer.indexOf('\n\n')) !== -1) {
86
+ const event = buffer.slice(0, separator);
87
+ buffer = buffer.slice(separator + 2);
88
+ const dataLines = event.split('\n')
89
+ .filter(line => line.startsWith('data:'))
90
+ .map(line => line.slice(5).trimStart());
91
+ if (dataLines.length === 0) {
92
+ send(`${event}\n\n`);
93
+ continue;
94
+ }
95
+ const payload = dataLines.join('\n');
96
+ if (payload === '[DONE]') {
97
+ for (const [index, state] of states) {
98
+ sendFallback(index, state);
99
+ }
100
+ send(`${event}\n\n`);
101
+ continue;
102
+ }
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(payload);
106
+ }
107
+ catch {
108
+ send(`${event}\n\n`);
109
+ continue;
110
+ }
111
+ for (const choice of parsed.choices ?? []) {
112
+ const index = choice.index ?? 0;
113
+ const state = getState(index);
114
+ const delta = choice.delta ?? {};
115
+ state.template = parsed;
116
+ if (hasContent(delta.content)) {
117
+ state.contentSeen = true;
118
+ }
119
+ if (typeof delta.reasoning_content === 'string') {
120
+ state.reasoning += delta.reasoning_content;
121
+ }
122
+ if (hasToolCalls(delta) || choice.finish_reason === 'tool_calls') {
123
+ state.toolsSeen = true;
124
+ }
125
+ if (choice.finish_reason != null) {
126
+ sendFallback(index, state);
127
+ }
128
+ }
129
+ // Keep the original SSE event unchanged.
130
+ send(`${event}\n\n`);
131
+ }
132
+ },
133
+ flush(controller) {
134
+ buffer += decoder.decode();
135
+ if (buffer) {
136
+ controller.enqueue(encoder.encode(buffer));
137
+ }
138
+ },
139
+ }));
140
+ }
141
+ function createReasoningFallbackFetch() {
142
+ return async (input, init) => {
143
+ const response = await fetch(input, init);
144
+ if (!response.ok)
145
+ return response;
146
+ const contentType = response.headers.get('content-type') ?? '';
147
+ const headers = new Headers(response.headers);
148
+ headers.delete('content-length');
149
+ headers.delete('content-encoding');
150
+ if (contentType.includes('text/event-stream') && response.body) {
151
+ return new Response(rewriteEventStream(response.body), {
152
+ status: response.status,
153
+ statusText: response.statusText,
154
+ headers,
155
+ });
156
+ }
157
+ if (!contentType.includes('application/json'))
158
+ return response;
159
+ const text = await response.text();
160
+ try {
161
+ const data = JSON.parse(text);
162
+ return new Response(JSON.stringify(rewriteCompletion(data)), {
163
+ status: response.status,
164
+ statusText: response.statusText,
165
+ headers,
166
+ });
167
+ }
168
+ catch {
169
+ return new Response(text, {
170
+ status: response.status,
171
+ statusText: response.statusText,
172
+ headers,
173
+ });
174
+ }
175
+ };
176
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-coding-stream",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "description": "OpenAI-compatible n8n Chat Model with native SSE streaming and dynamic Bearer + X-Request-By",
6
6
  "license": "MIT",
@@ -27,8 +27,8 @@
27
27
  ]
28
28
  },
29
29
  "dependencies": {
30
- "@langchain/openai": "^1.4.6",
31
- "@n8n/ai-utilities": "0.27.3"
30
+ "@langchain/core": "^1.2.18",
31
+ "@langchain/openai": "^1.4.6"
32
32
  },
33
33
  "devDependencies": {
34
34
  "n8n-workflow": "^2.16.0",