langflower 0.0.4 → 0.0.6
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 +20 -15
- package/docs/public/README.md +15 -0
- package/docs/public/configuration.md +56 -0
- package/docs/public/extending.md +58 -0
- package/docs/public/getting-started.md +61 -0
- package/docs/public/how-it-works.md +39 -0
- package/docs/public/product.md +43 -0
- package/docs/public/using-the-editor.md +53 -0
- package/docs/public/workflows.md +49 -0
- package/package.json +3 -2
- package/ui-dist/chunk-6DQ2XSRW.js +4 -0
- package/ui-dist/{chunk-2AICAC67.js → chunk-Y6VMBLSJ.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/main-RCFTD5AZ.js +311 -0
- package/ui-dist/styles-EOXODOI7.css +1 -0
- package/vendor/common-nodes/dist/ai/critique/node.d.ts +141 -23
- package/vendor/common-nodes/dist/ai/critique/node.js +33 -31
- package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +6 -6
- package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.d.ts +1 -1
- package/vendor/common-nodes/dist/ai/llm-compaction-ui-schema.js +2 -1
- package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.d.ts +7 -21
- package/vendor/common-nodes/dist/ai/llm-panel-ui-schema.js +8 -4
- package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +2 -0
- package/vendor/common-nodes/dist/ai/llm-session-shell.js +3 -3
- package/vendor/common-nodes/dist/ai/normalize-max-iterations.d.ts +4 -3
- package/vendor/common-nodes/dist/ai/normalize-max-iterations.js +4 -3
- package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.d.ts +2 -2
- package/vendor/common-nodes/dist/ai/openai/normalize-compaction-params.js +1 -1
- package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +9 -9
- package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.d.ts +10 -0
- package/vendor/common-nodes/dist/ai/resolve-chat-provider-model.js +8 -0
- package/vendor/common-nodes/dist/ai/review/node.d.ts +138 -15
- package/vendor/common-nodes/dist/ai/review/node.js +29 -22
- package/vendor/common-nodes/dist/ai/run-host-services.d.ts +8 -0
- package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +9 -9
- package/vendor/common-nodes/dist/ai/sub-agent/node.js +4 -3
- package/vendor/common-nodes/dist/hitl/chat-input/node.js +1 -0
- package/vendor/common-nodes/dist/hitl/review-gate/node.js +5 -0
- package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +2 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +1 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +5 -12
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +13 -0
- package/vendor/runtime/dist/runtime-runner.js +7 -4
- package/vendor/runtime/dist/runtime.d.ts +1 -1
- package/vendor/runtime/dist/types.d.ts +12 -0
- package/vendor/server/dist/bridge/attach-langflower-bridge.d.ts +4 -1
- package/vendor/server/dist/bridge/attach-langflower-bridge.js +16 -5
- package/vendor/server/dist/bridge/bridge-event-log.d.ts +12 -0
- package/vendor/server/dist/bridge/bridge-event-log.js +199 -0
- package/vendor/server/dist/bridge/build-execution-context.d.ts +1 -1
- package/vendor/server/dist/bridge/build-execution-context.js +3 -0
- package/vendor/server/dist/bridge/emit-bootstrap.d.ts +4 -2
- package/vendor/server/dist/bridge/emit-bootstrap.js +6 -2
- package/vendor/server/dist/bridge/settings-draft-controller.d.ts +14 -0
- package/vendor/server/dist/bridge/settings-draft-controller.js +178 -0
- package/vendor/server/dist/bridge/wire-config-handlers.d.ts +8 -1
- package/vendor/server/dist/bridge/wire-config-handlers.js +44 -16
- package/vendor/server/dist/bridge/wire-editor-handlers.d.ts +8 -1
- package/vendor/server/dist/bridge/wire-editor-handlers.js +40 -1
- package/vendor/server/dist/bridge/wire-runner-handlers.js +3 -1
- package/vendor/server/dist/config/langflower-config.service.d.ts +2 -0
- package/vendor/server/dist/config/langflower-config.service.js +24 -1
- package/vendor/server/dist/config/resolve-draft-provider-credentials.d.ts +18 -0
- package/vendor/server/dist/config/resolve-draft-provider-credentials.js +60 -0
- package/vendor/server/dist/create-server.js +5 -3
- package/vendor/server/dist/session/build-session-bootstrap.d.ts +3 -1
- package/vendor/server/dist/session/build-session-bootstrap.js +9 -0
- package/vendor/server/dist/session/langflower-session.d.ts +7 -2
- package/vendor/server/dist/session/langflower-session.js +9 -1
- package/vendor/server/dist/session/settings-draft-session.d.ts +24 -0
- package/vendor/server/dist/session/settings-draft-session.js +82 -0
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +4 -0
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +20 -8
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +5 -0
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +18 -16
- package/vendor/server/skeleton/workflows/simple-coder.json +249 -19
- package/vendor/server/skeleton/workflows/starter.json +31 -20
- package/vendor/shared/dist/langflower-bus-config.d.ts +50 -10
- package/vendor/shared/dist/langflower-bus-config.js +52 -11
- package/vendor/shared/dist/langflower-config/merge-langflower-config-layers.test.js +4 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.d.ts +14 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.js +38 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.d.ts +1 -0
- package/vendor/shared/dist/langflower-config/parse-default-chat-model.test.js +41 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.d.ts +6 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.js +5 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.d.ts +1 -0
- package/vendor/shared/dist/langflower-config/resolve-server-logs-enabled.test.js +11 -0
- package/vendor/shared/dist/langflower-config/settings-draft.d.ts +44 -0
- package/vendor/shared/dist/langflower-config/settings-draft.js +143 -0
- package/vendor/shared/dist/langflower.d.ts +7 -2
- package/vendor/shared/dist/langflower.js +3 -0
- package/vendor/shared/dist/types/langflower-bootstrap.d.ts +6 -1
- package/vendor/shared/dist/types/langflower-config.d.ts +65 -0
- package/vendor/shared/dist/types/langflower-editor.d.ts +19 -0
- package/ui-dist/chunk-6ZABW4JL.js +0 -4
- package/ui-dist/main-XI7IOZKZ.js +0 -311
- package/ui-dist/styles-TQGRAC7Q.css +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Settings draft: seed, patch, discard, save commit, connection probe.
|
|
3
|
+
*/
|
|
4
|
+
import { listProviderModels as listOpenAiProviderModels } from '@langflower/common-nodes/ai/openai/list-provider-models';
|
|
5
|
+
import { draftToSavePayload, mergeLangflowerConfigLayers, } from '@langflower/shared/langflower.js';
|
|
6
|
+
import { resolveDraftProviderCredentials } from '../config/resolve-draft-provider-credentials.js';
|
|
7
|
+
import { applyDraftPatch, buildDraftSnapshot, providerIndexesWithBaseUrl, seedScopeDraft, setConnectionStatus, } from '../session/settings-draft-session.js';
|
|
8
|
+
import { bridgeEmit, clientEmit } from './bridge-outbound.js';
|
|
9
|
+
import { buildLangflowerConfigSnapshot } from './build-langflower-config-snapshot.js';
|
|
10
|
+
const isScope = (value) => value === 'project' || value === 'global';
|
|
11
|
+
const layerForScope = (layers, scope) => (scope === 'project' ? layers.project : layers.global);
|
|
12
|
+
export const createSettingsDraftController = (bridge, context, session) => {
|
|
13
|
+
const probeGeneration = new Map();
|
|
14
|
+
const getState = (scope) => scope === 'project'
|
|
15
|
+
? session.settingsDraft.project
|
|
16
|
+
: session.settingsDraft.global;
|
|
17
|
+
const setState = (scope, state) => {
|
|
18
|
+
if (scope === 'project') {
|
|
19
|
+
session.settingsDraft.project = state;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
session.settingsDraft.global = state;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** Emit current session draft — never re-enters ensureSeeded/probe. */
|
|
26
|
+
const emitCurrent = (scope) => {
|
|
27
|
+
const state = getState(scope);
|
|
28
|
+
if (state === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
bridgeEmit(bridge, 'langflower.config.draft.snapshot', buildDraftSnapshot(scope, state));
|
|
32
|
+
};
|
|
33
|
+
const runProbe = async (scope, index) => {
|
|
34
|
+
const key = `${scope}:${index}`;
|
|
35
|
+
const generation = (probeGeneration.get(key) ?? 0) + 1;
|
|
36
|
+
probeGeneration.set(key, generation);
|
|
37
|
+
const state = getState(scope);
|
|
38
|
+
const row = state?.draft.providers[index];
|
|
39
|
+
if (state === undefined || row === undefined) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const baseURL = row.baseURL.trim();
|
|
43
|
+
if (baseURL.length === 0) {
|
|
44
|
+
setState(scope, setConnectionStatus(state, index, { state: 'idle' }));
|
|
45
|
+
emitCurrent(scope);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setState(scope, setConnectionStatus(state, index, { state: 'checking' }));
|
|
49
|
+
emitCurrent(scope);
|
|
50
|
+
const layers = await context.langflowerConfigService.readLayers();
|
|
51
|
+
const savedLayer = layerForScope(layers, scope);
|
|
52
|
+
const resolved = resolveDraftProviderCredentials(row, savedLayer);
|
|
53
|
+
let status;
|
|
54
|
+
if (!resolved.ok) {
|
|
55
|
+
status = { state: 'error', message: resolved.message };
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const listed = await listOpenAiProviderModels(resolved.credentials);
|
|
59
|
+
status =
|
|
60
|
+
listed.error !== undefined
|
|
61
|
+
? { state: 'error', message: listed.error }
|
|
62
|
+
: { state: 'ok', modelCount: listed.models.length };
|
|
63
|
+
}
|
|
64
|
+
if (probeGeneration.get(key) !== generation) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const latest = getState(scope);
|
|
68
|
+
if (latest === undefined ||
|
|
69
|
+
latest.draft.providers[index] === undefined) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setState(scope, setConnectionStatus(latest, index, status));
|
|
73
|
+
emitCurrent(scope);
|
|
74
|
+
};
|
|
75
|
+
const probeProvidersWithUrl = (scope, draft) => {
|
|
76
|
+
for (const index of providerIndexesWithBaseUrl(draft)) {
|
|
77
|
+
void runProbe(scope, index);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const ensureSeeded = async (scope) => {
|
|
81
|
+
if (getState(scope) !== undefined) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const layers = await context.langflowerConfigService.readLayers();
|
|
85
|
+
const seeded = seedScopeDraft(layerForScope(layers, scope));
|
|
86
|
+
setState(scope, seeded);
|
|
87
|
+
// Seed leaves URL rows as `checking`; kick probes so launch shows
|
|
88
|
+
// Connected/error without requiring an edit first.
|
|
89
|
+
probeProvidersWithUrl(scope, seeded.draft);
|
|
90
|
+
};
|
|
91
|
+
const snapshotFor = async (scope) => {
|
|
92
|
+
await ensureSeeded(scope);
|
|
93
|
+
const state = getState(scope);
|
|
94
|
+
if (state === undefined) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return buildDraftSnapshot(scope, state);
|
|
98
|
+
};
|
|
99
|
+
const broadcast = async (scope) => {
|
|
100
|
+
await ensureSeeded(scope);
|
|
101
|
+
emitCurrent(scope);
|
|
102
|
+
};
|
|
103
|
+
const pushToClient = async (client, scope) => {
|
|
104
|
+
const snapshot = await snapshotFor(scope);
|
|
105
|
+
if (snapshot === null) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
clientEmit(client, 'langflower.config.draft.snapshot', snapshot);
|
|
109
|
+
};
|
|
110
|
+
const patch = async (scope, draft) => {
|
|
111
|
+
await ensureSeeded(scope);
|
|
112
|
+
const previous = getState(scope);
|
|
113
|
+
if (previous === undefined) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const { next, probeIndexes } = applyDraftPatch(previous, draft);
|
|
117
|
+
setState(scope, next);
|
|
118
|
+
emitCurrent(scope);
|
|
119
|
+
for (const index of probeIndexes) {
|
|
120
|
+
void runProbe(scope, index);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const discard = async (scope) => {
|
|
124
|
+
const layers = await context.langflowerConfigService.readLayers();
|
|
125
|
+
const seeded = seedScopeDraft(layerForScope(layers, scope));
|
|
126
|
+
setState(scope, seeded);
|
|
127
|
+
emitCurrent(scope);
|
|
128
|
+
probeProvidersWithUrl(scope, seeded.draft);
|
|
129
|
+
};
|
|
130
|
+
const commitSave = async (scope, fallback) => {
|
|
131
|
+
await ensureSeeded(scope);
|
|
132
|
+
const state = getState(scope);
|
|
133
|
+
// Prefer an explicit Save payload (provider/model/serverLogs) so a
|
|
134
|
+
// trailing draft.patch cannot race past commit. Otherwise persist the
|
|
135
|
+
// session draft (`{ scope }` only).
|
|
136
|
+
const payload = fallback !== undefined &&
|
|
137
|
+
(fallback.provider !== undefined ||
|
|
138
|
+
fallback.model !== undefined ||
|
|
139
|
+
'serverLogs' in fallback)
|
|
140
|
+
? fallback
|
|
141
|
+
: state !== undefined
|
|
142
|
+
? draftToSavePayload(scope, state.draft)
|
|
143
|
+
: fallback;
|
|
144
|
+
if (payload === undefined || !isScope(payload.scope)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const layers = await context.langflowerConfigService.writeSettings({
|
|
148
|
+
scope: payload.scope,
|
|
149
|
+
...(payload.model !== undefined ? { model: payload.model } : {}),
|
|
150
|
+
...(payload.provider !== undefined
|
|
151
|
+
? { provider: payload.provider }
|
|
152
|
+
: {}),
|
|
153
|
+
...(payload.providerApiKeys !== undefined
|
|
154
|
+
? { providerApiKeys: payload.providerApiKeys }
|
|
155
|
+
: {}),
|
|
156
|
+
...('serverLogs' in payload
|
|
157
|
+
? { serverLogs: payload.serverLogs }
|
|
158
|
+
: {}),
|
|
159
|
+
});
|
|
160
|
+
const layer = layerForScope(layers, scope);
|
|
161
|
+
const seeded = seedScopeDraft(layer);
|
|
162
|
+
setState(scope, seeded);
|
|
163
|
+
const snapshot = await buildLangflowerConfigSnapshot(context);
|
|
164
|
+
bridgeEmit(bridge, 'langflower.config.snapshot', snapshot);
|
|
165
|
+
emitCurrent(scope);
|
|
166
|
+
probeProvidersWithUrl(scope, seeded.draft);
|
|
167
|
+
return mergeLangflowerConfigLayers(layers.global, layers.project);
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
ensureSeeded,
|
|
171
|
+
snapshotFor,
|
|
172
|
+
broadcast,
|
|
173
|
+
pushToClient,
|
|
174
|
+
patch,
|
|
175
|
+
discard,
|
|
176
|
+
commitSave,
|
|
177
|
+
};
|
|
178
|
+
};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { Subscription } from 'rxjs';
|
|
2
|
+
import type { LangflowerConfig } from '@langflower/shared/langflower.js';
|
|
2
3
|
import type { ServerContext } from '../server-context.js';
|
|
4
|
+
import type { LangflowerSession } from '../session/langflower-session.js';
|
|
3
5
|
import type { LangflowerBridge } from './langflower-bridge.types.js';
|
|
4
|
-
|
|
6
|
+
import type { SettingsDraftController } from './settings-draft-controller.js';
|
|
7
|
+
export type WireConfigHandlersOptions = {
|
|
8
|
+
readonly onEffectiveConfig?: (config: LangflowerConfig) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare const wireConfigHandlers: (bridge: LangflowerBridge, context: ServerContext, _session: LangflowerSession, draftController: SettingsDraftController, options?: WireConfigHandlersOptions) => Subscription;
|
|
11
|
+
export declare const applyServerLogsGate: (setEnabled: (enabled: boolean) => void, config: LangflowerConfig) => void;
|
|
@@ -1,21 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildLangflowerConfigSnapshot } from './build-langflower-config-snapshot.js';
|
|
1
|
+
import { resolveServerLogsEnabled } from '@langflower/shared/langflower.js';
|
|
3
2
|
import { isInboundEvent } from './inbound-guards.js';
|
|
4
3
|
import { broadcastModelsCatalog } from './push-models-catalog.js';
|
|
5
4
|
const isConfigScope = (value) => value === 'project' || value === 'global';
|
|
6
|
-
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
return;
|
|
5
|
+
const isSettingsDraft = (value) => {
|
|
6
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
7
|
+
return false;
|
|
10
8
|
}
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const draft = value;
|
|
10
|
+
return (typeof draft['defaultProviderId'] === 'string' &&
|
|
11
|
+
typeof draft['defaultModelId'] === 'string' &&
|
|
12
|
+
Array.isArray(draft['providers']) &&
|
|
13
|
+
(draft['serverLogs'] === 'off' ||
|
|
14
|
+
draft['serverLogs'] === 'default' ||
|
|
15
|
+
draft['serverLogs'] === 'on'));
|
|
16
|
+
};
|
|
17
|
+
export const wireConfigHandlers = (bridge, context, _session, draftController, options = {}) => {
|
|
18
|
+
const subscription = bridge['langflower.config.save.requested'].subscribe(async (raw) => {
|
|
19
|
+
if (!isInboundEvent(raw) ||
|
|
20
|
+
!isConfigScope(raw.payload.scope)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const effective = await draftController.commitSave(raw.payload.scope, raw.payload);
|
|
24
|
+
if (effective === null) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
options.onEffectiveConfig?.(effective);
|
|
28
|
+
await broadcastModelsCatalog(bridge, context);
|
|
17
29
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
subscription.add(bridge['langflower.config.draft.patch.requested'].subscribe(async (raw) => {
|
|
31
|
+
if (!isInboundEvent(raw) ||
|
|
32
|
+
!isConfigScope(raw.payload.scope) ||
|
|
33
|
+
!isSettingsDraft(raw.payload.draft)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await draftController.patch(raw.payload.scope, raw.payload.draft);
|
|
37
|
+
}));
|
|
38
|
+
subscription.add(bridge['langflower.config.draft.discard.requested'].subscribe(async (raw) => {
|
|
39
|
+
if (!isInboundEvent(raw) ||
|
|
40
|
+
!isConfigScope(raw.payload.scope)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await draftController.discard(raw.payload.scope);
|
|
44
|
+
}));
|
|
45
|
+
return subscription;
|
|
46
|
+
};
|
|
47
|
+
export const applyServerLogsGate = (setEnabled, config) => {
|
|
48
|
+
setEnabled(resolveServerLogsEnabled(config));
|
|
49
|
+
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { type EditorSettingsRequestedPayload, type EditorSettingsSnapshotPayload } from '@langflower/shared/langflower.js';
|
|
1
2
|
import { Subscription } from 'rxjs';
|
|
2
3
|
import type { ServerContext } from '../server-context.js';
|
|
3
4
|
import type { LangflowerSession } from '../session/langflower-session.js';
|
|
4
5
|
import type { LangflowerBridge } from './langflower-bridge.types.js';
|
|
5
|
-
|
|
6
|
+
import type { SettingsDraftController } from './settings-draft-controller.js';
|
|
7
|
+
export declare const wireEditorHandlers: (bridge: LangflowerBridge, context: ServerContext, session: LangflowerSession, draftController?: SettingsDraftController) => Subscription;
|
|
8
|
+
/**
|
|
9
|
+
* Apply a Settings aside intent. Returns `null` when the payload is invalid
|
|
10
|
+
* (`open: true` without a valid `scope`).
|
|
11
|
+
*/
|
|
12
|
+
export declare const applySettingsRequested: (current: EditorSettingsSnapshotPayload, payload: EditorSettingsRequestedPayload) => EditorSettingsSnapshotPayload | null;
|
|
@@ -19,7 +19,7 @@ const isValidCanvasViewport = (value) => {
|
|
|
19
19
|
Number.isFinite(viewport.scale) &&
|
|
20
20
|
viewport.scale > 0);
|
|
21
21
|
};
|
|
22
|
-
export const wireEditorHandlers = (bridge, context, session) => {
|
|
22
|
+
export const wireEditorHandlers = (bridge, context, session, draftController) => {
|
|
23
23
|
const subscription = new Subscription();
|
|
24
24
|
subscription.add(bridge['editor.addNode.requested'].subscribe((raw) => {
|
|
25
25
|
if (!isInboundEvent(raw)) {
|
|
@@ -182,6 +182,23 @@ export const wireEditorHandlers = (bridge, context, session) => {
|
|
|
182
182
|
bridgeEmit(bridge, 'editor.dividers.snapshot', clamped);
|
|
183
183
|
void context.langflowerConfigService.setDividerPositions(clamped);
|
|
184
184
|
}));
|
|
185
|
+
subscription.add(bridge['editor.settings.requested'].subscribe((raw) => {
|
|
186
|
+
if (!isInboundEvent(raw)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (findClientById(bridge, raw.clientId) === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const next = applySettingsRequested(session.settings, raw.payload);
|
|
193
|
+
if (next === null) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
session.settings = next;
|
|
197
|
+
bridgeEmit(bridge, 'editor.settings.snapshot', next);
|
|
198
|
+
if (next.open && draftController !== undefined) {
|
|
199
|
+
void draftController.broadcast(next.scope);
|
|
200
|
+
}
|
|
201
|
+
}));
|
|
185
202
|
subscription.add(bridge['editor.selectNode.requested'].subscribe((raw) => {
|
|
186
203
|
if (!isInboundEvent(raw)) {
|
|
187
204
|
return;
|
|
@@ -194,8 +211,30 @@ export const wireEditorHandlers = (bridge, context, session) => {
|
|
|
194
211
|
!session.activeWorkflow?.graph.nodes.some((node) => node.id === nodeId)) {
|
|
195
212
|
return;
|
|
196
213
|
}
|
|
214
|
+
if (nodeId !== null && session.settings.open) {
|
|
215
|
+
const closed = {
|
|
216
|
+
open: false,
|
|
217
|
+
scope: session.settings.scope,
|
|
218
|
+
};
|
|
219
|
+
session.settings = closed;
|
|
220
|
+
bridgeEmit(bridge, 'editor.settings.snapshot', closed);
|
|
221
|
+
}
|
|
197
222
|
session.selectedNodeId = nodeId;
|
|
198
223
|
bridgeEmit(bridge, 'editor.nodeSelected', buildSelectedNodePayload(session, context.resolveDefinition));
|
|
199
224
|
}));
|
|
200
225
|
return subscription;
|
|
201
226
|
};
|
|
227
|
+
const isConfigScope = (value) => value === 'project' || value === 'global';
|
|
228
|
+
/**
|
|
229
|
+
* Apply a Settings aside intent. Returns `null` when the payload is invalid
|
|
230
|
+
* (`open: true` without a valid `scope`).
|
|
231
|
+
*/
|
|
232
|
+
export const applySettingsRequested = (current, payload) => {
|
|
233
|
+
if (payload.open) {
|
|
234
|
+
if (!isConfigScope(payload.scope)) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return { open: true, scope: payload.scope };
|
|
238
|
+
}
|
|
239
|
+
return { open: false, scope: current.scope };
|
|
240
|
+
};
|
|
@@ -273,7 +273,9 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
|
|
|
273
273
|
if (connected === undefined) {
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
|
-
session.permissionAsks.reply(raw.payload)
|
|
276
|
+
if (session.permissionAsks.reply(raw.payload)) {
|
|
277
|
+
bridgeEmit(bridge, 'runner.permission.accepted', raw.payload);
|
|
278
|
+
}
|
|
277
279
|
}));
|
|
278
280
|
subscription.add(bridge['runner.executionFeed.clear.requested'].subscribe((raw) => {
|
|
279
281
|
if (!isInboundEvent(raw)) {
|
|
@@ -8,6 +8,8 @@ export type LangflowerConfigSettingsWrite = {
|
|
|
8
8
|
readonly model?: string;
|
|
9
9
|
readonly provider?: Readonly<Record<string, LangflowerProviderConfig>>;
|
|
10
10
|
readonly providerApiKeys?: Readonly<Record<string, string>>;
|
|
11
|
+
/** `null` clears the scope key (Settings Default). */
|
|
12
|
+
readonly serverLogs?: boolean | null;
|
|
11
13
|
};
|
|
12
14
|
export declare class LangflowerConfigService {
|
|
13
15
|
private readonly projectDir;
|
|
@@ -147,6 +147,9 @@ function parseLangflowerConfig(raw) {
|
|
|
147
147
|
? { currentWorkflowId: raw.currentWorkflowId }
|
|
148
148
|
: {}),
|
|
149
149
|
...(typeof raw.model === 'string' ? { model: raw.model } : {}),
|
|
150
|
+
...(typeof raw.serverLogs === 'boolean'
|
|
151
|
+
? { serverLogs: raw.serverLogs }
|
|
152
|
+
: {}),
|
|
150
153
|
...(providers !== undefined ? { provider: providers } : {}),
|
|
151
154
|
...(tools !== undefined ? { tools } : {}),
|
|
152
155
|
...(permission !== undefined ? { permission } : {}),
|
|
@@ -195,7 +198,20 @@ function mergeLangflowerConfig(existing, patch) {
|
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
if (patch.model !== undefined) {
|
|
198
|
-
|
|
201
|
+
if (patch.model.trim().length === 0) {
|
|
202
|
+
delete merged.model;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
merged.model = patch.model;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if ('serverLogs' in patch) {
|
|
209
|
+
if (patch.serverLogs === undefined) {
|
|
210
|
+
delete merged.serverLogs;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
merged.serverLogs = patch.serverLogs;
|
|
214
|
+
}
|
|
199
215
|
}
|
|
200
216
|
if (patch.provider !== undefined) {
|
|
201
217
|
merged.provider = patch.provider;
|
|
@@ -315,6 +331,13 @@ export class LangflowerConfigService {
|
|
|
315
331
|
const patch = {
|
|
316
332
|
...(input.model !== undefined ? { model: input.model } : {}),
|
|
317
333
|
...(provider !== undefined ? { provider } : {}),
|
|
334
|
+
...('serverLogs' in input
|
|
335
|
+
? {
|
|
336
|
+
serverLogs: input.serverLogs === null
|
|
337
|
+
? undefined
|
|
338
|
+
: input.serverLogs,
|
|
339
|
+
}
|
|
340
|
+
: {}),
|
|
318
341
|
};
|
|
319
342
|
const merged = mergeLangflowerConfig(existingRaw, patch);
|
|
320
343
|
await this.writeRawAt(filePath, merged);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve OpenAI-compatible credentials for a Settings draft row probe.
|
|
3
|
+
* Prefer pending draft apiKey; else fall back to the saved layer provider.
|
|
4
|
+
*/
|
|
5
|
+
import type { LangflowerConfig, ProviderDraft } from '@langflower/shared/langflower.js';
|
|
6
|
+
type ResolvedCredentials = {
|
|
7
|
+
readonly apiKey?: string;
|
|
8
|
+
readonly baseURL?: string;
|
|
9
|
+
};
|
|
10
|
+
export type ResolveDraftProviderCredentialsResult = {
|
|
11
|
+
readonly ok: true;
|
|
12
|
+
readonly credentials: ResolvedCredentials;
|
|
13
|
+
} | {
|
|
14
|
+
readonly ok: false;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const resolveDraftProviderCredentials: (row: ProviderDraft, savedLayer: LangflowerConfig) => ResolveDraftProviderCredentialsResult;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolveProviderCredentials } from './resolve-provider-credentials.js';
|
|
2
|
+
const ENV_REF_PATTERN = /^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
3
|
+
const resolveEnvRef = (value, context) => {
|
|
4
|
+
const match = ENV_REF_PATTERN.exec(value);
|
|
5
|
+
if (match === null) {
|
|
6
|
+
return { ok: true, value };
|
|
7
|
+
}
|
|
8
|
+
const varName = match[1];
|
|
9
|
+
const resolved = process.env[varName];
|
|
10
|
+
if (resolved === undefined || resolved === '') {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
message: `Environment variable ${varName} is not set (${context})`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { ok: true, value: resolved };
|
|
17
|
+
};
|
|
18
|
+
export const resolveDraftProviderCredentials = (row, savedLayer) => {
|
|
19
|
+
const baseURL = row.baseURL.trim();
|
|
20
|
+
if (baseURL.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
message: 'Base URL is empty',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const context = `draft provider ${row.id.trim() || '(new)'}`;
|
|
27
|
+
const pendingKey = row.apiKey.trim();
|
|
28
|
+
if (pendingKey.length > 0) {
|
|
29
|
+
const resolved = resolveEnvRef(pendingKey, context);
|
|
30
|
+
if (!resolved.ok) {
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
credentials: {
|
|
36
|
+
apiKey: resolved.value,
|
|
37
|
+
baseURL,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const providerId = row.id.trim();
|
|
42
|
+
if (providerId.length > 0 &&
|
|
43
|
+
savedLayer.provider?.[providerId] !== undefined) {
|
|
44
|
+
const fromSaved = resolveProviderCredentials(savedLayer, providerId);
|
|
45
|
+
if (!fromSaved.ok) {
|
|
46
|
+
return fromSaved;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
credentials: {
|
|
51
|
+
...fromSaved.credentials,
|
|
52
|
+
baseURL,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
credentials: { baseURL },
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasCustomNodePacks } from '@langflower/compiler/compile-project-nodes';
|
|
2
|
-
import { langflowerWsConfig, } from '@langflower/shared/langflower.js';
|
|
2
|
+
import { langflowerWsConfig, resolveServerLogsEnabled, } from '@langflower/shared/langflower.js';
|
|
3
3
|
import { createServer as createWsBridge } from '@langflower/websocket-bridge/create-server';
|
|
4
4
|
import express from 'express';
|
|
5
5
|
import http from 'node:http';
|
|
@@ -33,23 +33,25 @@ export async function createServer(options) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
const toolConfig = await context.configService.read();
|
|
36
|
+
const langflowerConfig = await context.langflowerConfigService.read();
|
|
36
37
|
const port = options.port ?? toolConfig.port;
|
|
37
38
|
const httpServer = http.createServer(app);
|
|
38
39
|
const bridge = createWsBridge(langflowerWsConfig, { httpServer });
|
|
39
40
|
const detachBridge = attachLangflowerBridge(bridge, context, {
|
|
41
|
+
serverLogsEnabled: resolveServerLogsEnabled(langflowerConfig),
|
|
40
42
|
...(options.onRunSettled !== undefined
|
|
41
43
|
? { onRunSettled: options.onRunSettled }
|
|
42
44
|
: {}),
|
|
43
45
|
});
|
|
44
46
|
httpServer.on('close', () => {
|
|
45
|
-
detachBridge();
|
|
47
|
+
void detachBridge();
|
|
46
48
|
bridge.close();
|
|
47
49
|
});
|
|
48
50
|
try {
|
|
49
51
|
await listenHttpServer(httpServer, port, '127.0.0.1');
|
|
50
52
|
}
|
|
51
53
|
catch (error) {
|
|
52
|
-
detachBridge();
|
|
54
|
+
await detachBridge();
|
|
53
55
|
bridge.close();
|
|
54
56
|
httpServer.close();
|
|
55
57
|
throw error;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { SessionStateSnapshotPayload } from '@langflower/shared/langflower.js';
|
|
1
|
+
import type { EditorSettingsSnapshotPayload, SessionStateSnapshotPayload } from '@langflower/shared/langflower.js';
|
|
2
2
|
import type { LangflowerConfigService } from '../config/langflower-config.service.js';
|
|
3
3
|
import type { ResolveNodeDefinition } from '../workflow/workflow-document.js';
|
|
4
4
|
import { LangflowerSession } from './langflower-session.js';
|
|
5
|
+
/** Empty effective providers → open Global Settings for bootstrap onboarding. */
|
|
6
|
+
export declare const settingsAsideForEffectiveConfig: (provider: Readonly<Record<string, unknown>> | undefined, current: EditorSettingsSnapshotPayload) => EditorSettingsSnapshotPayload;
|
|
5
7
|
export declare function buildSessionBootstrap(session: LangflowerSession, langflowerConfigService: LangflowerConfigService, resolveDefinition: ResolveNodeDefinition, projectDir: string): Promise<SessionStateSnapshotPayload>;
|
|
@@ -2,12 +2,21 @@ import { redactLangflowerConfigForBridge } from '../config/redact-langflower-con
|
|
|
2
2
|
import { withSkillsCatalog } from '../skills/with-skills-catalog.js';
|
|
3
3
|
import { buildSelectedNodePayload } from './build-selected-node-payload.js';
|
|
4
4
|
import { LangflowerSession } from './langflower-session.js';
|
|
5
|
+
/** Empty effective providers → open Global Settings for bootstrap onboarding. */
|
|
6
|
+
export const settingsAsideForEffectiveConfig = (provider, current) => {
|
|
7
|
+
if (Object.keys(provider ?? {}).length === 0) {
|
|
8
|
+
return { open: true, scope: 'global' };
|
|
9
|
+
}
|
|
10
|
+
return current;
|
|
11
|
+
};
|
|
5
12
|
export async function buildSessionBootstrap(session, langflowerConfigService, resolveDefinition, projectDir) {
|
|
6
13
|
const langflowerConfig = redactLangflowerConfigForBridge(await withSkillsCatalog(projectDir, await langflowerConfigService.read()));
|
|
14
|
+
session.settings = settingsAsideForEffectiveConfig(langflowerConfig.provider, session.settings);
|
|
7
15
|
return {
|
|
8
16
|
version: LangflowerSession.snapshotVersion,
|
|
9
17
|
langflowerConfig,
|
|
10
18
|
dividerPositions: session.dividerPositions,
|
|
11
19
|
selectedNode: buildSelectedNodePayload(session, resolveDefinition).node,
|
|
20
|
+
settings: session.settings,
|
|
12
21
|
};
|
|
13
22
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RunId, RuntimeRunnerStatus } from '@langflower/runtime';
|
|
2
2
|
import { RuntimeFacade } from '@langflower/runtime';
|
|
3
|
-
import type { DividerPositions, ExecutionFeedSnapshotPayload, WorkflowCurrentStatus, WorkflowLoadedPayload } from '@langflower/shared/langflower.js';
|
|
3
|
+
import type { DividerPositions, EditorSettingsSnapshotPayload, ExecutionFeedSnapshotPayload, WorkflowCurrentStatus, WorkflowLoadedPayload } from '@langflower/shared/langflower.js';
|
|
4
4
|
import { PendingPermissionAsks } from '../harness/pending-permission-asks.js';
|
|
5
|
+
import { type SettingsDraftStore } from './settings-draft-session.js';
|
|
5
6
|
export declare class LangflowerSession {
|
|
6
7
|
readonly runtime: RuntimeFacade;
|
|
7
8
|
/** Feed permission.ask pause/resume for the internal tool loop. */
|
|
@@ -15,6 +16,10 @@ export declare class LangflowerSession {
|
|
|
15
16
|
dividerPositions: DividerPositions;
|
|
16
17
|
/** In-memory canvas selection, shared across tabs. `null` = nothing selected. */
|
|
17
18
|
selectedNodeId: string | null;
|
|
19
|
+
/** Settings aside chrome — session memory only (not persisted to disk). */
|
|
20
|
+
settings: EditorSettingsSnapshotPayload;
|
|
21
|
+
/** Unsaved Settings form draft per scope (session memory). */
|
|
22
|
+
settingsDraft: SettingsDraftStore;
|
|
18
23
|
private readonly subscriptions;
|
|
19
24
|
private mcpDispose;
|
|
20
25
|
constructor();
|
|
@@ -27,5 +32,5 @@ export declare class LangflowerSession {
|
|
|
27
32
|
markPristine(): void;
|
|
28
33
|
buildExecutionFeed(): ExecutionFeedSnapshotPayload | null;
|
|
29
34
|
static readonly sessionReadyVersion = 1;
|
|
30
|
-
static readonly snapshotVersion =
|
|
35
|
+
static readonly snapshotVersion = 6;
|
|
31
36
|
}
|
|
@@ -2,8 +2,9 @@ import { RuntimeFacade } from '@langflower/runtime';
|
|
|
2
2
|
import { deriveExecutionProgressStatus } from '@langflower/shared/langflower.js';
|
|
3
3
|
import { Subscription } from 'rxjs';
|
|
4
4
|
import { PendingPermissionAsks } from '../harness/pending-permission-asks.js';
|
|
5
|
+
import { emptySettingsDraftStore, } from './settings-draft-session.js';
|
|
5
6
|
const SESSION_READY_VERSION = 1;
|
|
6
|
-
const SNAPSHOT_VERSION =
|
|
7
|
+
const SNAPSHOT_VERSION = 6;
|
|
7
8
|
export class LangflowerSession {
|
|
8
9
|
runtime = new RuntimeFacade({ log: true });
|
|
9
10
|
/** Feed permission.ask pause/resume for the internal tool loop. */
|
|
@@ -21,6 +22,13 @@ export class LangflowerSession {
|
|
|
21
22
|
};
|
|
22
23
|
/** In-memory canvas selection, shared across tabs. `null` = nothing selected. */
|
|
23
24
|
selectedNodeId = null;
|
|
25
|
+
/** Settings aside chrome — session memory only (not persisted to disk). */
|
|
26
|
+
settings = {
|
|
27
|
+
open: false,
|
|
28
|
+
scope: 'project',
|
|
29
|
+
};
|
|
30
|
+
/** Unsaved Settings form draft per scope (session memory). */
|
|
31
|
+
settingsDraft = emptySettingsDraftStore();
|
|
24
32
|
subscriptions = new Subscription();
|
|
25
33
|
mcpDispose;
|
|
26
34
|
constructor() {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory Settings draft per scope (session memory, not disk).
|
|
3
|
+
*/
|
|
4
|
+
import type { LangflowerConfig, LangflowerConfigDraftSnapshotPayload, LangflowerConfigScope, ProviderConnectionStatus, SettingsDraft } from '@langflower/shared/langflower.js';
|
|
5
|
+
export type ScopeSettingsDraft = {
|
|
6
|
+
readonly draft: SettingsDraft;
|
|
7
|
+
readonly baseline: SettingsDraft;
|
|
8
|
+
readonly connections: Readonly<Record<string, ProviderConnectionStatus>>;
|
|
9
|
+
};
|
|
10
|
+
export type SettingsDraftStore = {
|
|
11
|
+
project: ScopeSettingsDraft | undefined;
|
|
12
|
+
global: ScopeSettingsDraft | undefined;
|
|
13
|
+
};
|
|
14
|
+
export declare const emptySettingsDraftStore: () => SettingsDraftStore;
|
|
15
|
+
export declare const idleConnectionsForDraft: (draft: SettingsDraft) => Readonly<Record<string, ProviderConnectionStatus>>;
|
|
16
|
+
export declare const seedScopeDraft: (layerConfig: LangflowerConfig) => ScopeSettingsDraft;
|
|
17
|
+
/** Indexes of draft rows that should be probed (non-empty Base URL). */
|
|
18
|
+
export declare const providerIndexesWithBaseUrl: (draft: SettingsDraft) => readonly number[];
|
|
19
|
+
export declare const buildDraftSnapshot: (scope: LangflowerConfigScope, state: ScopeSettingsDraft) => LangflowerConfigDraftSnapshotPayload;
|
|
20
|
+
export declare const applyDraftPatch: (previous: ScopeSettingsDraft, incoming: SettingsDraft) => {
|
|
21
|
+
readonly next: ScopeSettingsDraft;
|
|
22
|
+
readonly probeIndexes: readonly number[];
|
|
23
|
+
};
|
|
24
|
+
export declare const setConnectionStatus: (state: ScopeSettingsDraft, index: number, status: ProviderConnectionStatus) => ScopeSettingsDraft;
|