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.
Files changed (76) hide show
  1. package/README.md +0 -1
  2. package/dist/agents/agent-tool.js +4 -4
  3. package/dist/agents/agent-tool.js.map +1 -1
  4. package/dist/core/query-engine.d.ts +10 -0
  5. package/dist/core/query-engine.js +72 -16
  6. package/dist/core/query-engine.js.map +1 -1
  7. package/dist/core/query.d.ts +1 -0
  8. package/dist/core/query.js +5 -0
  9. package/dist/core/query.js.map +1 -1
  10. package/dist/core/run-agent.js +45 -36
  11. package/dist/core/run-agent.js.map +1 -1
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/model/agent-settings-boundary.d.ts +11 -0
  16. package/dist/model/agent-settings-boundary.js +52 -0
  17. package/dist/model/agent-settings-boundary.js.map +1 -0
  18. package/dist/model/anthropic-adapter.d.ts +24 -0
  19. package/dist/model/anthropic-adapter.js +106 -0
  20. package/dist/model/anthropic-adapter.js.map +1 -0
  21. package/dist/model/anthropic-mapper.d.ts +14 -0
  22. package/dist/model/anthropic-mapper.js +340 -0
  23. package/dist/model/anthropic-mapper.js.map +1 -0
  24. package/dist/model/composition-runtime.d.ts +31 -0
  25. package/dist/model/composition-runtime.js +31 -0
  26. package/dist/model/composition-runtime.js.map +1 -0
  27. package/dist/model/config.d.ts +10 -3
  28. package/dist/model/config.js +26 -10
  29. package/dist/model/config.js.map +1 -1
  30. package/dist/model/context-window.d.ts +16 -1
  31. package/dist/model/context-window.js +171 -5
  32. package/dist/model/context-window.js.map +1 -1
  33. package/dist/model/env.js +0 -1
  34. package/dist/model/env.js.map +1 -1
  35. package/dist/model/model-gateway.d.ts +3 -1
  36. package/dist/model/model-gateway.js +4 -0
  37. package/dist/model/model-gateway.js.map +1 -1
  38. package/dist/model/model-metadata.json +9 -3
  39. package/dist/model/openai-adapter.js +2 -1
  40. package/dist/model/openai-adapter.js.map +1 -1
  41. package/dist/model/provider-factory.js +28 -1
  42. package/dist/model/provider-factory.js.map +1 -1
  43. package/dist/model/settings-files.d.ts +3 -0
  44. package/dist/model/settings-files.js +43 -0
  45. package/dist/model/settings-files.js.map +1 -0
  46. package/dist/model/settings-store.d.ts +84 -0
  47. package/dist/model/settings-store.js +191 -0
  48. package/dist/model/settings-store.js.map +1 -0
  49. package/dist/model/settings-sync.d.ts +23 -0
  50. package/dist/model/settings-sync.js +68 -0
  51. package/dist/model/settings-sync.js.map +1 -0
  52. package/dist/repl/commands.d.ts +1 -1
  53. package/dist/repl/commands.js +3 -2
  54. package/dist/repl/commands.js.map +1 -1
  55. package/dist/repl/index.js +50 -22
  56. package/dist/repl/index.js.map +1 -1
  57. package/dist/session/session-store.d.ts +2 -0
  58. package/dist/session/session-store.js +7 -1
  59. package/dist/session/session-store.js.map +1 -1
  60. package/dist/tasks/task-persistence.js +2 -1
  61. package/dist/tasks/task-persistence.js.map +1 -1
  62. package/dist/tools/builtins/image-generation-tool.js +2 -0
  63. package/dist/tools/builtins/image-generation-tool.js.map +1 -1
  64. package/dist/tools/builtins/search-providers.js +2 -0
  65. package/dist/tools/builtins/search-providers.js.map +1 -1
  66. package/dist/tools/builtins/search-tool.d.ts +2 -0
  67. package/dist/tools/builtins/search-tool.js +2 -2
  68. package/dist/tools/builtins/search-tool.js.map +1 -1
  69. package/dist/web/index.d.ts +10 -3
  70. package/dist/web/index.js +126 -33
  71. package/dist/web/index.js.map +1 -1
  72. package/dist/web/model-settings-api.d.ts +9 -0
  73. package/dist/web/model-settings-api.js +221 -0
  74. package/dist/web/model-settings-api.js.map +1 -0
  75. package/package.json +2 -2
  76. package/scripts/initialize-model-metadata.mjs +6 -0
@@ -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, ReasoningEffort } from "../model/model-gateway.js";
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]: ReasoningEffort[];
742
+ [k: string]: string[];
743
+ };
744
+ modelReasoningEnabled: {
745
+ [k: string]: boolean;
739
746
  };
740
747
  reasoning: string[];
741
748
  envPath: string;
742
- } | undefined;
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, createModelGatewayFromProcessEnv } from "../model/provider-factory.js";
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
- const modelConfig = readModelProviderConfig(process.env);
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(createModelGatewayFromProcessEnv(process.env), communicationLogger);
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: includeCatalog ? webCatalog(this.runtime) : undefined,
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" || !["none", "minimal", "low", "medium", "high", "xhigh", "max", "default", "off"].includes(reasoning))) {
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
- values = { ...values, apiKey: parseEnvFileSafe(this.runtime.envPath).OPENAI_API_KEY || process.env.OPENAI_API_KEY || "" };
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 modelIds = [...new Set(loadModelCatalog().models.flatMap((model) => model.modelIds.length ? model.modelIds : [model.id]))].sort((left, right) => left.localeCompare(right));
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
- modelReasoning: Object.fromEntries(modelIds.map(model => [model, reasoningEffortsForModel(model) ?? []])),
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: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
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(_env) {
3503
- return currentModelProvider();
3598
+ function guessLoginProvider(env) {
3599
+ return env.ANTHROPIC_API_KEY && !env.OPENAI_API_KEY ? "anthropic" : currentModelProvider();
3504
3600
  }
3505
- function defaultBaseUrlForLoginProvider(_provider) {
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(_provider) {
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(_provider) {
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)