neoctl 0.2.52 → 0.2.54
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 +0 -1
- package/dist/agents/agent-tool.js +4 -4
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/core/query-engine.d.ts +10 -0
- package/dist/core/query-engine.js +72 -16
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +1 -0
- package/dist/core/query.js +5 -0
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.js +45 -36
- package/dist/core/run-agent.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/agent-settings-boundary.d.ts +11 -0
- package/dist/model/agent-settings-boundary.js +52 -0
- package/dist/model/agent-settings-boundary.js.map +1 -0
- package/dist/model/anthropic-adapter.d.ts +24 -0
- package/dist/model/anthropic-adapter.js +106 -0
- package/dist/model/anthropic-adapter.js.map +1 -0
- package/dist/model/anthropic-mapper.d.ts +14 -0
- package/dist/model/anthropic-mapper.js +340 -0
- package/dist/model/anthropic-mapper.js.map +1 -0
- package/dist/model/composition-runtime.d.ts +31 -0
- package/dist/model/composition-runtime.js +31 -0
- package/dist/model/composition-runtime.js.map +1 -0
- package/dist/model/config.d.ts +10 -3
- package/dist/model/config.js +26 -10
- package/dist/model/config.js.map +1 -1
- package/dist/model/context-window.d.ts +16 -1
- package/dist/model/context-window.js +171 -5
- package/dist/model/context-window.js.map +1 -1
- package/dist/model/env.js +0 -1
- package/dist/model/env.js.map +1 -1
- package/dist/model/model-gateway.d.ts +3 -1
- package/dist/model/model-gateway.js +4 -0
- package/dist/model/model-gateway.js.map +1 -1
- package/dist/model/model-metadata.json +9 -3
- package/dist/model/openai-adapter.js +2 -1
- package/dist/model/openai-adapter.js.map +1 -1
- package/dist/model/provider-factory.js +28 -1
- package/dist/model/provider-factory.js.map +1 -1
- package/dist/model/settings-files.d.ts +3 -0
- package/dist/model/settings-files.js +43 -0
- package/dist/model/settings-files.js.map +1 -0
- package/dist/model/settings-store.d.ts +84 -0
- package/dist/model/settings-store.js +191 -0
- package/dist/model/settings-store.js.map +1 -0
- package/dist/model/settings-sync.d.ts +23 -0
- package/dist/model/settings-sync.js +68 -0
- package/dist/model/settings-sync.js.map +1 -0
- package/dist/repl/commands.d.ts +1 -1
- package/dist/repl/commands.js +3 -2
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +50 -22
- package/dist/repl/index.js.map +1 -1
- package/dist/session/session-store.d.ts +2 -0
- package/dist/session/session-store.js +7 -1
- package/dist/session/session-store.js.map +1 -1
- package/dist/tasks/task-persistence.js +2 -1
- package/dist/tasks/task-persistence.js.map +1 -1
- package/dist/tools/builtins/image-generation-tool.js +2 -0
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/search-providers.js +2 -0
- package/dist/tools/builtins/search-providers.js.map +1 -1
- package/dist/tools/builtins/search-tool.d.ts +2 -0
- package/dist/tools/builtins/search-tool.js +2 -2
- package/dist/tools/builtins/search-tool.js.map +1 -1
- package/dist/web/index.d.ts +10 -3
- package/dist/web/index.js +126 -33
- package/dist/web/index.js.map +1 -1
- package/dist/web/model-settings-api.d.ts +9 -0
- package/dist/web/model-settings-api.js +221 -0
- package/dist/web/model-settings-api.js.map +1 -0
- package/package.json +2 -2
- package/scripts/initialize-model-metadata.mjs +6 -0
package/dist/web/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { QueryEngine } from "../core/query-engine.js";
|
|
|
11
11
|
import { type AppPromptStore } from "../app/app-prompt.js";
|
|
12
12
|
import { type ModelProviderName } from "../model/config.js";
|
|
13
13
|
import { CommunicationLogger, LoggingModelGateway } from "../model/communication-logger.js";
|
|
14
|
-
import type { ModelUsage, ReasoningConfig
|
|
14
|
+
import type { ModelUsage, ReasoningConfig } from "../model/model-gateway.js";
|
|
15
15
|
import { ToolRegistry } from "../tools/registry.js";
|
|
16
16
|
import type { Tool, ToolUseContext } from "../tools/tool.js";
|
|
17
17
|
import { type ToolPresentation } from "../tools/tool-catalog.js";
|
|
@@ -406,6 +406,7 @@ export declare function createWebRuntimeRouter(options?: WebRuntimeRouterOptions
|
|
|
406
406
|
export declare class WebRepl {
|
|
407
407
|
private runtime;
|
|
408
408
|
private readonly demoOptions;
|
|
409
|
+
replCommandsEnabled: boolean;
|
|
409
410
|
currentSessionId(): string | undefined;
|
|
410
411
|
private deletingSession;
|
|
411
412
|
private sessionMutations;
|
|
@@ -450,6 +451,7 @@ export declare class WebRepl {
|
|
|
450
451
|
isDemo(engine?: QueryEngine): boolean;
|
|
451
452
|
startDemo(expectedSessionId: string): Promise<WebActionResult>;
|
|
452
453
|
private recordDemoInput;
|
|
454
|
+
private readonly settingsListener;
|
|
453
455
|
constructor(runtime: WebRuntime, demoOptions?: {
|
|
454
456
|
wait?: DemoWait;
|
|
455
457
|
});
|
|
@@ -725,6 +727,7 @@ export declare class WebRepl {
|
|
|
725
727
|
modelSettings: {
|
|
726
728
|
model: string | undefined;
|
|
727
729
|
reasoning: ReasoningConfig | null | undefined;
|
|
730
|
+
imageInput: boolean | undefined;
|
|
728
731
|
};
|
|
729
732
|
pendingSessionSettings: import("../core/query.js").SessionSettingsPatch & {
|
|
730
733
|
sessionPrompt?: import("../core/session-settings-prompt.js").SessionPromptState | null;
|
|
@@ -734,12 +737,16 @@ export declare class WebRepl {
|
|
|
734
737
|
catalog: {
|
|
735
738
|
commands: import("../repl/commands.js").ReplCommandDefinition[];
|
|
736
739
|
modelIds: string[];
|
|
740
|
+
modelProtocol: "openai" | "anthropic" | undefined;
|
|
737
741
|
modelReasoning: {
|
|
738
|
-
[k: string]:
|
|
742
|
+
[k: string]: string[];
|
|
743
|
+
};
|
|
744
|
+
modelReasoningEnabled: {
|
|
745
|
+
[k: string]: boolean;
|
|
739
746
|
};
|
|
740
747
|
reasoning: string[];
|
|
741
748
|
envPath: string;
|
|
742
|
-
}
|
|
749
|
+
};
|
|
743
750
|
interactive: {
|
|
744
751
|
sessions: boolean;
|
|
745
752
|
login: LoginFormPayload;
|
package/dist/web/index.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { trackSettingsGateway } from "../model/settings-sync.js";
|
|
3
|
+
import { observeModelSettings } from "../model/settings-sync.js";
|
|
4
|
+
import { modelProtocols } from "../model/context-window.js";
|
|
5
|
+
import { isReasoningEffort } from "../model/model-gateway.js";
|
|
6
|
+
import { ModelSettingsStore, compositionModelConfig } from "../model/settings-store.js";
|
|
7
|
+
import { registerCompositionTools } from "../model/composition-runtime.js";
|
|
8
|
+
import { handleModelSettingsRequest } from "./model-settings-api.js";
|
|
2
9
|
import { paginateSessions, sessionPageOptions } from "../session/session-pagination.js";
|
|
3
10
|
import { applyToolInputChanges } from "../model/tool-input-stream.js";
|
|
4
11
|
import { toolActivity } from "./tool-activity.js";
|
|
@@ -29,7 +36,7 @@ import { loadDefaultDotEnvFiles } from "../model/env.js";
|
|
|
29
36
|
import { readModelProviderConfig } from "../model/config.js";
|
|
30
37
|
import { findModelMetadata, loadModelCatalog, reasoningEffortsForModel, resolveContextWindowTokens } from "../model/context-window.js";
|
|
31
38
|
import { CommunicationLogger, LoggingModelGateway } from "../model/communication-logger.js";
|
|
32
|
-
import { createModelGatewayFromConfig
|
|
39
|
+
import { createModelGatewayFromConfig } from "../model/provider-factory.js";
|
|
33
40
|
import { ToolRegistry, isToolEnabledByDefault } from "../tools/registry.js";
|
|
34
41
|
import { builtinToolPresentation, toolPresentationForSource } from "../tools/tool-catalog.js";
|
|
35
42
|
import { editTool, writeTool } from "../tools/builtins/edit-tool.js";
|
|
@@ -147,9 +154,13 @@ function parseWebArgs(argv) {
|
|
|
147
154
|
}
|
|
148
155
|
export async function createWebRuntime(options = {}) {
|
|
149
156
|
const envLoad = loadDefaultDotEnvFiles({ override: true });
|
|
150
|
-
|
|
157
|
+
loadModelCatalog();
|
|
158
|
+
const settingsStore = new ModelSettingsStore();
|
|
159
|
+
const appliedSettings = settingsStore.read()?.applied;
|
|
160
|
+
const settingsRevisions = new WeakMap();
|
|
161
|
+
const modelConfig = appliedSettings ? compositionModelConfig(appliedSettings.composition, appliedSettings.connections) : readModelProviderConfig(process.env);
|
|
151
162
|
const communicationLogger = new CommunicationLogger();
|
|
152
|
-
const modelGateway = new LoggingModelGateway(
|
|
163
|
+
const modelGateway = trackSettingsGateway(new LoggingModelGateway(createModelGatewayFromConfig(modelConfig), communicationLogger), appliedSettings?.revision);
|
|
153
164
|
const taskStore = new TaskStore();
|
|
154
165
|
const pluginCatalog = normalizeWebRuntimePlugins(options.externalPlugins);
|
|
155
166
|
const pluginOverrides = normalizePluginOverrides(options.sessionPluginOverrides, pluginCatalog);
|
|
@@ -179,6 +190,13 @@ export async function createWebRuntime(options = {}) {
|
|
|
179
190
|
registerTool(createLoadImageTool(), { source: "builtin" });
|
|
180
191
|
if (modelConfig?.provider === "openai")
|
|
181
192
|
registerTool(createOpenAIImageGenerationTool(), { source: "builtin" });
|
|
193
|
+
if (appliedSettings)
|
|
194
|
+
registerCompositionTools(tools, appliedSettings.composition, appliedSettings.connections);
|
|
195
|
+
for (const name of ["web_search", "image_create"]) {
|
|
196
|
+
const tool = tools.get(name);
|
|
197
|
+
if (tool)
|
|
198
|
+
catalogTool(tool, { source: "builtin" });
|
|
199
|
+
}
|
|
182
200
|
registerTool(planTool, { source: "builtin" });
|
|
183
201
|
const frameworkFeedback = new FrameworkFeedbackStore(options.frameworkFeedbackDir);
|
|
184
202
|
registerTool(createFrameworkFeedbackTool(frameworkFeedback), { source: "builtin" });
|
|
@@ -217,6 +235,41 @@ export async function createWebRuntime(options = {}) {
|
|
|
217
235
|
tools,
|
|
218
236
|
contextManagerFactory: (cwd) => new DefaultContextManager({ cwd }),
|
|
219
237
|
acquireTurnResources: (engine) => acquireWebPluginTurn(runtime, true, engine),
|
|
238
|
+
beforeQuery: (engine) => {
|
|
239
|
+
const applied = settingsStore.read()?.applied;
|
|
240
|
+
const captureTools = () => {
|
|
241
|
+
const snapshot = tools.clone();
|
|
242
|
+
compositionToolSnapshots.set(engine, snapshot);
|
|
243
|
+
const session = engine.snapshot().session?.sessionId;
|
|
244
|
+
if (session)
|
|
245
|
+
sessionCompositionSnapshots.set(session, new WeakRef(engine));
|
|
246
|
+
};
|
|
247
|
+
if (!applied) {
|
|
248
|
+
if (!compositionToolSnapshots.has(engine))
|
|
249
|
+
captureTools();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const revision = applied.revision;
|
|
253
|
+
if (settingsRevisions.get(engine) === revision)
|
|
254
|
+
return;
|
|
255
|
+
const config = compositionModelConfig(applied.composition, applied.connections);
|
|
256
|
+
if (!config)
|
|
257
|
+
return;
|
|
258
|
+
const gateway = trackSettingsGateway(new LoggingModelGateway(createModelGatewayFromConfig(config), communicationLogger), revision);
|
|
259
|
+
engine.setModelProvider({ modelGateway: gateway, model: config.model, reasoning: config.defaultReasoning });
|
|
260
|
+
effectiveModelProviders.set(engine, config.provider);
|
|
261
|
+
registerCompositionTools(tools, applied.composition, applied.connections);
|
|
262
|
+
for (const name of ["web_search", "image_create"]) {
|
|
263
|
+
const tool = tools.get(name);
|
|
264
|
+
if (tool)
|
|
265
|
+
catalogTool(tool, { source: "builtin" });
|
|
266
|
+
}
|
|
267
|
+
runtime.modelGateway = gateway;
|
|
268
|
+
runtime.agentRuntime.modelGateway = gateway;
|
|
269
|
+
runtime.defaultReasoning = config.defaultReasoning;
|
|
270
|
+
settingsRevisions.set(engine, revision);
|
|
271
|
+
captureTools();
|
|
272
|
+
},
|
|
220
273
|
refreshTools: (engine) => {
|
|
221
274
|
if (runtime)
|
|
222
275
|
refreshRuntimePlugins(runtime, engine);
|
|
@@ -353,6 +406,8 @@ function resolveRuntimePluginOverrides(runtime, engine, sessionId = engine.snaps
|
|
|
353
406
|
return support.overrides;
|
|
354
407
|
}
|
|
355
408
|
/** Children acquire independent tools/prompts; they never refresh the parent's in-flight context. */
|
|
409
|
+
const compositionToolSnapshots = new WeakMap();
|
|
410
|
+
const sessionCompositionSnapshots = new Map();
|
|
356
411
|
function acquireWebPluginTurn(runtime, main, engine = runtime.engine, sessionId) {
|
|
357
412
|
if (main) {
|
|
358
413
|
refreshRuntimePlugins(runtime, engine);
|
|
@@ -364,6 +419,22 @@ function acquireWebPluginTurn(runtime, main, engine = runtime.engine, sessionId)
|
|
|
364
419
|
try {
|
|
365
420
|
const catalog = normalizeWebRuntimePlugins(snapshot?.plugins ?? support?.resolveCatalog?.() ?? support?.catalog);
|
|
366
421
|
const tools = buildPluginRegistry(runtime, catalog, overrides);
|
|
422
|
+
const owner = sessionId ? sessionCompositionSnapshots.get(sessionId)?.deref() : engine;
|
|
423
|
+
const frozen = owner ? compositionToolSnapshots.get(owner)?.clone() : undefined;
|
|
424
|
+
if (sessionId && !owner)
|
|
425
|
+
sessionCompositionSnapshots.delete(sessionId);
|
|
426
|
+
if (frozen)
|
|
427
|
+
for (const name of ["web_search", "image_create"]) {
|
|
428
|
+
const enabled = tools.isEnabled(name) ?? frozen.isEnabled(name);
|
|
429
|
+
frozen.setEnabled(name, true);
|
|
430
|
+
const tool = frozen.get(name);
|
|
431
|
+
tools.unregister(name);
|
|
432
|
+
if (tool) {
|
|
433
|
+
tools.register(tool);
|
|
434
|
+
if (enabled === false)
|
|
435
|
+
tools.setEnabled(name, false);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
367
438
|
return { tools, release: snapshot?.release ?? (() => { }),
|
|
368
439
|
...(!main ? { promptSections: [...(support?.basePromptSections ?? []), ...activeWebRuntimePlugins(catalog, overrides).flatMap(plugin => plugin.promptSections ?? [])] } : {}) };
|
|
369
440
|
}
|
|
@@ -612,6 +683,7 @@ function webRuntimeScopeKey(scope) {
|
|
|
612
683
|
export class WebRepl {
|
|
613
684
|
runtime;
|
|
614
685
|
demoOptions;
|
|
686
|
+
replCommandsEnabled = true;
|
|
615
687
|
currentSessionId() { return this.runtime.engine.snapshot().session?.sessionId; }
|
|
616
688
|
deletingSession = false;
|
|
617
689
|
sessionMutations = 0;
|
|
@@ -746,9 +818,17 @@ export class WebRepl {
|
|
|
746
818
|
this.demoMessageQueues.delete(engine);
|
|
747
819
|
}
|
|
748
820
|
}
|
|
821
|
+
settingsListener = () => {
|
|
822
|
+
if (this.deletingSession)
|
|
823
|
+
return;
|
|
824
|
+
this.runtime.engine.refreshModelConfigurationIfIdle();
|
|
825
|
+
for (const subscriber of this.subscribers)
|
|
826
|
+
this.send(subscriber, "sync", this.snapshot());
|
|
827
|
+
};
|
|
749
828
|
constructor(runtime, demoOptions = {}) {
|
|
750
829
|
this.runtime = runtime;
|
|
751
830
|
this.demoOptions = demoOptions;
|
|
831
|
+
observeModelSettings(this.settingsListener);
|
|
752
832
|
const lineId = { current: 0 };
|
|
753
833
|
this.lines = initialLines(runtime, lineId);
|
|
754
834
|
this.lineId = lineId.current;
|
|
@@ -829,6 +909,7 @@ export class WebRepl {
|
|
|
829
909
|
}
|
|
830
910
|
snapshot(includeCatalog = false) {
|
|
831
911
|
const backgroundTasks = this.backgroundTasks();
|
|
912
|
+
this.runtime.engine.refreshModelConfigurationIfIdle?.();
|
|
832
913
|
const engineSnapshot = this.runtime.engine.snapshot();
|
|
833
914
|
const pendingSessionSettings = this.runtime.engine.getPendingSessionSettings?.() ?? {};
|
|
834
915
|
// Include retained reasoning alongside a queued model. Explicit undefined is
|
|
@@ -854,11 +935,11 @@ export class WebRepl {
|
|
|
854
935
|
backgroundSessionRunCount: this.backgroundSessionRuns.size,
|
|
855
936
|
runningSessionIds: this.runningSessionIds(),
|
|
856
937
|
session: engineSnapshot.session,
|
|
857
|
-
modelSettings: { model: engineSnapshot.model, reasoning: engineSnapshot.reasoning },
|
|
938
|
+
modelSettings: { model: engineSnapshot.model, reasoning: engineSnapshot.reasoning, imageInput: findModelMetadata(engineSnapshot.model || "")?.imageInput },
|
|
858
939
|
pendingSessionSettings,
|
|
859
940
|
fastMode: this.runtime.engine.isFastMode(),
|
|
860
941
|
appPrompt: this.runtime.engine.getAppPrompt(),
|
|
861
|
-
catalog:
|
|
942
|
+
catalog: webCatalog(this.runtime),
|
|
862
943
|
interactive: includeCatalog ? webInteractiveCatalog(this.runtime) : undefined,
|
|
863
944
|
};
|
|
864
945
|
}
|
|
@@ -1113,7 +1194,7 @@ export class WebRepl {
|
|
|
1113
1194
|
async submit(text, attachments = []) {
|
|
1114
1195
|
if (this.deletingSession)
|
|
1115
1196
|
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1116
|
-
if (this.runControl().canResume && (!text.trimStart().startsWith("/") || !text.trim()))
|
|
1197
|
+
if (this.runControl().canResume && (!this.replCommandsEnabled || !text.trimStart().startsWith("/") || !text.trim()))
|
|
1117
1198
|
return this.continueRun(text, attachments);
|
|
1118
1199
|
if (this.pendingContinuation)
|
|
1119
1200
|
return { ok: false, error: "续接请求已接收,请勿重复发送" };
|
|
@@ -1124,7 +1205,7 @@ export class WebRepl {
|
|
|
1124
1205
|
const trimmed = text.trim();
|
|
1125
1206
|
if (!trimmed && attachments.length === 0)
|
|
1126
1207
|
return { ok: true };
|
|
1127
|
-
const command = parseReplCommand(text);
|
|
1208
|
+
const command = this.replCommandsEnabled ? parseReplCommand(text) : { type: "input", text };
|
|
1128
1209
|
if (this.busy && command.type === "new") {
|
|
1129
1210
|
await this.detachRunningForeground("new session");
|
|
1130
1211
|
}
|
|
@@ -1456,11 +1537,15 @@ export class WebRepl {
|
|
|
1456
1537
|
if (typeof model !== "string" || !model.trim() || model.length > 256 || /[\s\x00-\x1f]/u.test(model.trim())) {
|
|
1457
1538
|
return actionFailure("MODEL_INVALID", "invalid model");
|
|
1458
1539
|
}
|
|
1459
|
-
if (reasoning !== undefined && (typeof reasoning !== "string" || !
|
|
1540
|
+
if (reasoning !== undefined && (typeof reasoning !== "string" || !(reasoning === "default" || reasoning === "off" || isReasoningEffort(reasoning)))) {
|
|
1460
1541
|
return actionFailure("MODEL_INVALID", "invalid reasoning effort");
|
|
1461
1542
|
}
|
|
1462
1543
|
const engine = this.runtime.engine;
|
|
1463
1544
|
const nextModel = model.trim();
|
|
1545
|
+
const provider = effectiveModelProviders.get(engine) ?? readModelProviderConfig()?.provider;
|
|
1546
|
+
const metadata = (engine.getActiveModelCatalog?.() ?? loadModelCatalog()).models.find(m => m.modelIds.includes(nextModel));
|
|
1547
|
+
if (metadata && provider && !modelProtocols(metadata).includes(provider))
|
|
1548
|
+
return actionFailure("MODEL_INVALID", "模型不支持当前主连接接口");
|
|
1464
1549
|
const current = { ...engine.getModelSettings(), ...engine.getPendingSessionSettings() };
|
|
1465
1550
|
const argument = reasoning;
|
|
1466
1551
|
const validationError = validateModelReasoningArgument(nextModel, argument);
|
|
@@ -1566,9 +1651,10 @@ export class WebRepl {
|
|
|
1566
1651
|
async saveLogin(providerValue, values) {
|
|
1567
1652
|
const provider = parseLoginProvider(providerValue);
|
|
1568
1653
|
if (!provider)
|
|
1569
|
-
return actionFailure("LOGIN_INVALID", "provider must be openai");
|
|
1654
|
+
return actionFailure("LOGIN_INVALID", "provider must be openai or anthropic");
|
|
1570
1655
|
if (dockerEnabled() && !values.apiKey?.trim()) {
|
|
1571
|
-
|
|
1656
|
+
const key = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
1657
|
+
values = { ...values, apiKey: parseEnvFileSafe(this.runtime.envPath)[key] || process.env[key] || "" };
|
|
1572
1658
|
}
|
|
1573
1659
|
const payload = { ...createLoginFormPayload(this.runtime.envPath, provider), provider, values };
|
|
1574
1660
|
const validationError = validateLoginFormPayload(payload);
|
|
@@ -2327,7 +2413,7 @@ export class WebRepl {
|
|
|
2327
2413
|
return imageIds.length === pending.length && imageIds.every((imageId, index) => imageId === pending[index]);
|
|
2328
2414
|
}
|
|
2329
2415
|
async handleCommandOrPrompt(text, attachments = [], continuationOnly = false) {
|
|
2330
|
-
const command = parseReplCommand(text);
|
|
2416
|
+
const command = this.replCommandsEnabled ? parseReplCommand(text) : { type: "input", text };
|
|
2331
2417
|
if (command.type === "exit") {
|
|
2332
2418
|
this.append(systemLine("neo web server is still running. Close this tab or stop the server process with Ctrl+C."));
|
|
2333
2419
|
return;
|
|
@@ -2420,7 +2506,7 @@ export class WebRepl {
|
|
|
2420
2506
|
this.append({ kind: "error", text: "/secret is currently available in the interactive REPL only." });
|
|
2421
2507
|
return;
|
|
2422
2508
|
}
|
|
2423
|
-
if (text.trimStart().startsWith("/")) {
|
|
2509
|
+
if (this.replCommandsEnabled && text.trimStart().startsWith("/")) {
|
|
2424
2510
|
this.append({ kind: "error", text: `Unknown or incomplete command: ${text.trim()}\nType /help for commands.` });
|
|
2425
2511
|
return;
|
|
2426
2512
|
}
|
|
@@ -2962,6 +3048,8 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2962
3048
|
}
|
|
2963
3049
|
if (req.method === "POST" && url.pathname === "/api/sessions/new")
|
|
2964
3050
|
return sendJson(res, await repl.newSession());
|
|
3051
|
+
if (await handleModelSettingsRequest(req, res, url))
|
|
3052
|
+
return;
|
|
2965
3053
|
if (req.method === "GET" && url.pathname === "/api/login")
|
|
2966
3054
|
return sendJson(res, repl.loginForm(url.searchParams.get("provider") ?? undefined));
|
|
2967
3055
|
if (req.method === "POST" && url.pathname === "/api/login") {
|
|
@@ -3082,12 +3170,18 @@ function sanitizeWebAttachments(value) {
|
|
|
3082
3170
|
return [];
|
|
3083
3171
|
});
|
|
3084
3172
|
}
|
|
3173
|
+
const effectiveModelProviders = new WeakMap();
|
|
3085
3174
|
function webCatalog(runtime) {
|
|
3086
|
-
const
|
|
3175
|
+
const catalog = runtime.engine.getActiveModelCatalog?.() ?? loadModelCatalog();
|
|
3176
|
+
const provider = effectiveModelProviders.get(runtime.engine) ?? readModelProviderConfig()?.provider;
|
|
3177
|
+
const models = catalog.models.filter(model => !provider || modelProtocols(model).includes(provider));
|
|
3178
|
+
const modelIds = [...new Set(models.flatMap(model => model.modelIds.length ? model.modelIds : [model.id]))].sort((left, right) => left.localeCompare(right, "en", { numeric: true }));
|
|
3087
3179
|
return {
|
|
3088
3180
|
commands: replCommandDefinitions,
|
|
3089
3181
|
modelIds,
|
|
3090
|
-
|
|
3182
|
+
modelProtocol: provider,
|
|
3183
|
+
modelReasoning: Object.fromEntries(modelIds.map(model => [model, reasoningEffortsForModel(model, catalog) ?? []])),
|
|
3184
|
+
modelReasoningEnabled: Object.fromEntries(models.flatMap(m => m.modelIds.map(id => [id, m.reasoning !== false]))),
|
|
3091
3185
|
reasoning: ["none", "minimal", "low", "medium", "high", "xhigh", "max", "default", "off"],
|
|
3092
3186
|
envPath: runtime.envPath,
|
|
3093
3187
|
};
|
|
@@ -3447,28 +3541,32 @@ async function persistModelCommandSettings(runtime, command, reasoningUpdate) {
|
|
|
3447
3541
|
return { providerChanged: targetProvider !== currentProvider };
|
|
3448
3542
|
}
|
|
3449
3543
|
function currentModelProvider() {
|
|
3450
|
-
return "openai";
|
|
3544
|
+
return parseLoginProvider(process.env.MODEL_PROVIDER) ?? "openai";
|
|
3451
3545
|
}
|
|
3452
3546
|
function parseLoginProvider(value) {
|
|
3453
|
-
if (value === "openai")
|
|
3547
|
+
if (value === "openai" || value === "anthropic")
|
|
3454
3548
|
return value;
|
|
3455
3549
|
return undefined;
|
|
3456
3550
|
}
|
|
3457
|
-
const LOGIN_PROVIDERS = ["openai"];
|
|
3551
|
+
const LOGIN_PROVIDERS = ["openai", "anthropic"];
|
|
3458
3552
|
const SHARED_LOGIN_FIELDS = [
|
|
3459
3553
|
{ key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
|
|
3460
|
-
{ key: "
|
|
3461
|
-
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared" },
|
|
3554
|
+
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared", placeholder: "Leave empty for server default" },
|
|
3462
3555
|
{ key: "timeoutMs", label: "Timeout ms", envKey: "MODEL_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
3463
3556
|
{ key: "streamIdleTimeoutMs", label: "Stream idle timeout ms", envKey: "MODEL_STREAM_IDLE_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
3464
3557
|
{ key: "maxRetries", label: "Max retries", envKey: "MODEL_MAX_RETRIES", scope: "shared", placeholder: "2" },
|
|
3465
3558
|
];
|
|
3466
3559
|
const LOGIN_FIELD_DEFINITIONS = {
|
|
3560
|
+
anthropic: [
|
|
3561
|
+
{ key: "apiKey", label: "API / CPA key", envKey: "ANTHROPIC_API_KEY", scope: "provider", required: true, secret: true },
|
|
3562
|
+
{ key: "baseUrl", label: "Base URL", envKey: "ANTHROPIC_BASE_URL", scope: "provider", placeholder: "https://api.anthropic.com" },
|
|
3563
|
+
{ key: "model", label: "Model", envKey: "ANTHROPIC_MODEL", scope: "provider", required: true, placeholder: "claude-sonnet-4-6" },
|
|
3564
|
+
...SHARED_LOGIN_FIELDS.map(field => field.key === "reasoningEffort" ? { ...field, options: ["", "off", "none", "low", "medium", "high", "xhigh", "max"] } : field.key === "maxOutputTokens" ? { ...field, placeholder: "8192" } : field),
|
|
3565
|
+
],
|
|
3467
3566
|
openai: [
|
|
3468
3567
|
{ key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
3469
3568
|
{ key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
|
|
3470
3569
|
{ key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.6" },
|
|
3471
|
-
{ key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
|
|
3472
3570
|
...SHARED_LOGIN_FIELDS,
|
|
3473
3571
|
],
|
|
3474
3572
|
};
|
|
@@ -3495,18 +3593,16 @@ function loginValuesForProvider(provider, env) {
|
|
|
3495
3593
|
values.baseUrl = defaultBaseUrlForLoginProvider(provider);
|
|
3496
3594
|
if (!values.model)
|
|
3497
3595
|
values.model = defaultModelForLoginProvider(provider);
|
|
3498
|
-
if (provider === "openai" && !values.endpoint)
|
|
3499
|
-
values.endpoint = "auto";
|
|
3500
3596
|
return values;
|
|
3501
3597
|
}
|
|
3502
|
-
function guessLoginProvider(
|
|
3503
|
-
return currentModelProvider();
|
|
3598
|
+
function guessLoginProvider(env) {
|
|
3599
|
+
return env.ANTHROPIC_API_KEY && !env.OPENAI_API_KEY ? "anthropic" : currentModelProvider();
|
|
3504
3600
|
}
|
|
3505
|
-
function defaultBaseUrlForLoginProvider(
|
|
3506
|
-
return "https://api.openai.com";
|
|
3601
|
+
function defaultBaseUrlForLoginProvider(provider) {
|
|
3602
|
+
return provider === "anthropic" ? "https://api.anthropic.com" : "https://api.openai.com";
|
|
3507
3603
|
}
|
|
3508
|
-
function defaultModelForLoginProvider(
|
|
3509
|
-
return "gpt-5.6";
|
|
3604
|
+
function defaultModelForLoginProvider(provider) {
|
|
3605
|
+
return provider === "anthropic" ? "claude-sonnet-4-6" : "gpt-5.6";
|
|
3510
3606
|
}
|
|
3511
3607
|
function validateLoginFormPayload(payload) {
|
|
3512
3608
|
for (const field of LOGIN_FIELD_DEFINITIONS[payload.provider]) {
|
|
@@ -3521,9 +3617,6 @@ function validateLoginFormPayload(payload) {
|
|
|
3521
3617
|
if (value && !Number.isFinite(Number(value)))
|
|
3522
3618
|
return `${fieldKey} must be a number.`;
|
|
3523
3619
|
}
|
|
3524
|
-
if (payload.provider === "openai" && payload.values.endpoint === "chat" && payload.values.reasoningSummary?.trim()) {
|
|
3525
|
-
return "Reasoning summary requires the Responses API endpoint.";
|
|
3526
|
-
}
|
|
3527
3620
|
return undefined;
|
|
3528
3621
|
}
|
|
3529
3622
|
async function saveLoginPayloadToEnv(payload) {
|
|
@@ -3558,8 +3651,8 @@ function stripEnvQuotes(value) {
|
|
|
3558
3651
|
return value.slice(1, -1);
|
|
3559
3652
|
return value;
|
|
3560
3653
|
}
|
|
3561
|
-
function modelEnvKeyForProvider(
|
|
3562
|
-
return "OPENAI_MODEL";
|
|
3654
|
+
function modelEnvKeyForProvider(provider) {
|
|
3655
|
+
return provider === "anthropic" ? "ANTHROPIC_MODEL" : "OPENAI_MODEL";
|
|
3563
3656
|
}
|
|
3564
3657
|
function envValueForReasoning(reasoning) {
|
|
3565
3658
|
if (reasoning === null)
|