n8n-nodes-coding-stream 0.1.6 → 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,6 +1,8 @@
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
7
  const n8n_workflow_1 = require("n8n-workflow");
6
8
  class CodingStreamChatModel {
@@ -22,10 +24,10 @@ class CodingStreamChatModel {
22
24
  name: 'baseURL',
23
25
  type: 'string',
24
26
  required: true,
25
- default: 'https://external-ai-gateway.sovcombank.group/llm/v1',
27
+ default: '',
26
28
  description: 'Base URL that ends in /v1 (the SDK adds /chat/completions)',
27
29
  },
28
- { displayName: 'Model', name: 'model', type: 'string', required: true, default: 'coding-ultima' },
30
+ { displayName: 'Model', name: 'model', type: 'string', required: true, default: '' },
29
31
  {
30
32
  displayName: 'Bearer Token',
31
33
  name: 'bearerToken',
@@ -104,6 +106,7 @@ class CodingStreamChatModel {
104
106
  configuration: {
105
107
  baseURL,
106
108
  defaultHeaders: { 'X-Request-By': requestBy },
109
+ fetch: (0, reasoningFallback_1.createReasoningFallbackFetch)(),
107
110
  },
108
111
  temperature: options.temperature ?? 0.3,
109
112
  ...(options.maxTokens != null ? { maxTokens: Number(options.maxTokens) } : {}),
@@ -113,7 +116,7 @@ class CodingStreamChatModel {
113
116
  timeout: options.timeout ?? 120000,
114
117
  maxRetries: 0,
115
118
  supportsStrictToolCalling: false,
116
- callbacks: [],
119
+ callbacks: [new N8nModelTracing_1.N8nModelTracing(this)],
117
120
  });
118
121
  return { response: model };
119
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.6",
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,6 +27,7 @@
27
27
  ]
28
28
  },
29
29
  "dependencies": {
30
+ "@langchain/core": "^1.2.18",
30
31
  "@langchain/openai": "^1.4.6"
31
32
  },
32
33
  "devDependencies": {