@nextclaw/server 0.5.17 → 0.5.19

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/dist/index.d.ts CHANGED
@@ -147,6 +147,49 @@ type RuntimeConfigUpdate = {
147
147
  bindings?: AgentBindingView[];
148
148
  session?: SessionConfigView;
149
149
  };
150
+ type SecretSourceView = "env" | "file" | "exec";
151
+ type SecretRefView = {
152
+ source: SecretSourceView;
153
+ provider?: string;
154
+ id: string;
155
+ };
156
+ type SecretProviderEnvView = {
157
+ source: "env";
158
+ prefix?: string;
159
+ };
160
+ type SecretProviderFileView = {
161
+ source: "file";
162
+ path: string;
163
+ format?: "json";
164
+ };
165
+ type SecretProviderExecView = {
166
+ source: "exec";
167
+ command: string;
168
+ args?: string[];
169
+ cwd?: string;
170
+ timeoutMs?: number;
171
+ };
172
+ type SecretProviderView = SecretProviderEnvView | SecretProviderFileView | SecretProviderExecView;
173
+ type SecretsView = {
174
+ enabled: boolean;
175
+ defaults: {
176
+ env?: string;
177
+ file?: string;
178
+ exec?: string;
179
+ };
180
+ providers: Record<string, SecretProviderView>;
181
+ refs: Record<string, SecretRefView>;
182
+ };
183
+ type SecretsConfigUpdate = {
184
+ enabled?: boolean;
185
+ defaults?: {
186
+ env?: string | null;
187
+ file?: string | null;
188
+ exec?: string | null;
189
+ };
190
+ providers?: Record<string, SecretProviderView> | null;
191
+ refs?: Record<string, SecretRefView> | null;
192
+ };
150
193
  type ChatTurnRequest = {
151
194
  message: string;
152
195
  sessionKey?: string;
@@ -163,6 +206,16 @@ type ChatTurnResult = {
163
206
  model?: string;
164
207
  metadata?: Record<string, unknown>;
165
208
  };
209
+ type ChatTurnStreamEvent = {
210
+ type: "delta";
211
+ delta: string;
212
+ } | {
213
+ type: "final";
214
+ result: ChatTurnResult;
215
+ } | {
216
+ type: "error";
217
+ error: string;
218
+ };
166
219
  type ChatTurnView = {
167
220
  reply: string;
168
221
  sessionKey: string;
@@ -174,6 +227,7 @@ type ChatTurnView = {
174
227
  };
175
228
  type UiChatRuntime = {
176
229
  processTurn: (params: ChatTurnRequest) => Promise<ChatTurnResult>;
230
+ processTurnStream?: (params: ChatTurnRequest) => AsyncGenerator<ChatTurnStreamEvent>;
177
231
  };
178
232
  type ConfigView = {
179
233
  agents: {
@@ -206,6 +260,7 @@ type ConfigView = {
206
260
  tools?: Record<string, unknown>;
207
261
  gateway?: Record<string, unknown>;
208
262
  ui?: Record<string, unknown>;
263
+ secrets?: SecretsView;
209
264
  };
210
265
  type ProviderSpecView = {
211
266
  name: string;
@@ -522,5 +577,6 @@ declare function getSessionHistory(configPath: string, key: string, limit?: numb
522
577
  declare function patchSession(configPath: string, key: string, patch: SessionPatchUpdate): SessionHistoryView | null;
523
578
  declare function deleteSession(configPath: string, key: string): boolean;
524
579
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
580
+ declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
525
581
 
526
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderSpecView, type RuntimeConfigUpdate, type SessionConfigView, type SessionEntryView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, updateChannel, updateModel, updateProvider, updateRuntime };
582
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  buildConfigSchema,
24
24
  findProviderByName,
25
25
  getPackageVersion,
26
+ hasSecretRef,
26
27
  isSensitiveConfigPath,
27
28
  SessionManager,
28
29
  getWorkspacePathFromConfig
@@ -230,7 +231,9 @@ function maskApiKey(value) {
230
231
  apiKeyMasked: `${value.slice(0, 2)}****${value.slice(-4)}`
231
232
  };
232
233
  }
233
- function toProviderView(provider, providerName, uiHints, spec) {
234
+ function toProviderView(config, provider, providerName, uiHints, spec) {
235
+ const apiKeyPath = `providers.${providerName}.apiKey`;
236
+ const apiKeyRefSet = hasSecretRef(config, apiKeyPath);
234
237
  const masked = maskApiKey(provider.apiKey);
235
238
  const extraHeaders = provider.extraHeaders && Object.keys(provider.extraHeaders).length > 0 ? sanitizePublicConfigValue(
236
239
  provider.extraHeaders,
@@ -238,8 +241,8 @@ function toProviderView(provider, providerName, uiHints, spec) {
238
241
  uiHints
239
242
  ) : null;
240
243
  const view = {
241
- apiKeySet: masked.apiKeySet,
242
- apiKeyMasked: masked.apiKeyMasked,
244
+ apiKeySet: masked.apiKeySet || apiKeyRefSet,
245
+ apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
243
246
  apiBase: provider.apiBase ?? null,
244
247
  extraHeaders: extraHeaders && Object.keys(extraHeaders).length > 0 ? extraHeaders : null
245
248
  };
@@ -253,7 +256,7 @@ function buildConfigView(config) {
253
256
  const providers = {};
254
257
  for (const [name, provider] of Object.entries(config.providers)) {
255
258
  const spec = findProviderByName(name);
256
- providers[name] = toProviderView(provider, name, uiHints, spec);
259
+ providers[name] = toProviderView(config, provider, name, uiHints, spec);
257
260
  }
258
261
  return {
259
262
  agents: config.agents,
@@ -267,9 +270,20 @@ function buildConfigView(config) {
267
270
  session: sanitizePublicConfigValue(config.session, "session", uiHints),
268
271
  tools: sanitizePublicConfigValue(config.tools, "tools", uiHints),
269
272
  gateway: sanitizePublicConfigValue(config.gateway, "gateway", uiHints),
270
- ui: sanitizePublicConfigValue(config.ui, "ui", uiHints)
273
+ ui: sanitizePublicConfigValue(config.ui, "ui", uiHints),
274
+ secrets: {
275
+ enabled: config.secrets.enabled,
276
+ defaults: { ...config.secrets.defaults },
277
+ providers: { ...config.secrets.providers },
278
+ refs: { ...config.secrets.refs }
279
+ }
271
280
  };
272
281
  }
282
+ function clearSecretRef(config, path) {
283
+ if (config.secrets.refs[path]) {
284
+ delete config.secrets.refs[path];
285
+ }
286
+ }
273
287
  function buildConfigMeta(config) {
274
288
  const providers = PROVIDERS.map((spec) => ({
275
289
  name: spec.name,
@@ -369,6 +383,7 @@ function updateProvider(configPath, providerName, patch) {
369
383
  const spec = findProviderByName(providerName);
370
384
  if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
371
385
  provider.apiKey = patch.apiKey ?? "";
386
+ clearSecretRef(config, `providers.${providerName}.apiKey`);
372
387
  }
373
388
  if (Object.prototype.hasOwnProperty.call(patch, "apiBase")) {
374
389
  provider.apiBase = patch.apiBase ?? null;
@@ -383,7 +398,7 @@ function updateProvider(configPath, providerName, patch) {
383
398
  saveConfig(next, configPath);
384
399
  const uiHints = buildUiHints(next);
385
400
  const updated = next.providers[providerName];
386
- return toProviderView(updated, providerName, uiHints, spec ?? void 0);
401
+ return toProviderView(next, updated, providerName, uiHints, spec ?? void 0);
387
402
  }
388
403
  function updateChannel(configPath, channelName, patch) {
389
404
  const config = loadConfigOrDefault(configPath);
@@ -391,6 +406,12 @@ function updateChannel(configPath, channelName, patch) {
391
406
  if (!channel) {
392
407
  return null;
393
408
  }
409
+ for (const key of Object.keys(patch)) {
410
+ const path = `channels.${channelName}.${key}`;
411
+ if (isSensitivePath(path)) {
412
+ clearSecretRef(config, path);
413
+ }
414
+ }
394
415
  config.channels[channelName] = { ...channel, ...patch };
395
416
  const next = ConfigSchema.parse(config);
396
417
  saveConfig(next, configPath);
@@ -575,6 +596,41 @@ function updateRuntime(configPath, patch) {
575
596
  session: view.session ?? {}
576
597
  };
577
598
  }
599
+ function updateSecrets(configPath, patch) {
600
+ const config = loadConfigOrDefault(configPath);
601
+ if (Object.prototype.hasOwnProperty.call(patch, "enabled")) {
602
+ config.secrets.enabled = Boolean(patch.enabled);
603
+ }
604
+ if (patch.defaults) {
605
+ const nextDefaults = { ...config.secrets.defaults };
606
+ for (const source of ["env", "file", "exec"]) {
607
+ if (!Object.prototype.hasOwnProperty.call(patch.defaults, source)) {
608
+ continue;
609
+ }
610
+ const value = patch.defaults[source];
611
+ if (typeof value === "string" && value.trim()) {
612
+ nextDefaults[source] = value.trim();
613
+ } else {
614
+ delete nextDefaults[source];
615
+ }
616
+ }
617
+ config.secrets.defaults = nextDefaults;
618
+ }
619
+ if (Object.prototype.hasOwnProperty.call(patch, "providers")) {
620
+ config.secrets.providers = patch.providers ?? {};
621
+ }
622
+ if (Object.prototype.hasOwnProperty.call(patch, "refs")) {
623
+ config.secrets.refs = patch.refs ?? {};
624
+ }
625
+ const next = ConfigSchema.parse(config);
626
+ saveConfig(next, configPath);
627
+ return {
628
+ enabled: next.secrets.enabled,
629
+ defaults: { ...next.secrets.defaults },
630
+ providers: { ...next.secrets.providers },
631
+ refs: { ...next.secrets.refs }
632
+ };
633
+ }
578
634
 
579
635
  // src/ui/router.ts
580
636
  var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
@@ -763,6 +819,24 @@ function resolveAgentIdFromSessionKey(sessionKey) {
763
819
  const agentId = readNonEmptyString(parsed?.agentId);
764
820
  return agentId;
765
821
  }
822
+ function buildChatTurnView(params) {
823
+ const completedAt = /* @__PURE__ */ new Date();
824
+ return {
825
+ reply: String(params.result.reply ?? ""),
826
+ sessionKey: readNonEmptyString(params.result.sessionKey) ?? params.fallbackSessionKey,
827
+ ...readNonEmptyString(params.result.agentId) || params.requestedAgentId ? { agentId: readNonEmptyString(params.result.agentId) ?? params.requestedAgentId } : {},
828
+ ...readNonEmptyString(params.result.model) || params.requestedModel ? { model: readNonEmptyString(params.result.model) ?? params.requestedModel } : {},
829
+ requestedAt: params.requestedAt.toISOString(),
830
+ completedAt: completedAt.toISOString(),
831
+ durationMs: Math.max(0, completedAt.getTime() - params.startedAtMs)
832
+ };
833
+ }
834
+ function toSseFrame(event, data) {
835
+ return `event: ${event}
836
+ data: ${JSON.stringify(data)}
837
+
838
+ `;
839
+ }
766
840
  function normalizeMarketplaceBaseUrl(options) {
767
841
  const fromOptions = options.marketplace?.apiBaseUrl?.trim();
768
842
  const fromEnv = process.env.NEXTCLAW_MARKETPLACE_API_BASE?.trim();
@@ -1506,6 +1580,15 @@ function createUiRouter(options) {
1506
1580
  options.publish({ type: "config.updated", payload: { path: `channels.${channel}` } });
1507
1581
  return c.json(ok(result));
1508
1582
  });
1583
+ app.put("/api/config/secrets", async (c) => {
1584
+ const body = await readJson(c.req.raw);
1585
+ if (!body.ok) {
1586
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1587
+ }
1588
+ const result = updateSecrets(options.configPath, body.data);
1589
+ options.publish({ type: "config.updated", payload: { path: "secrets" } });
1590
+ return c.json(ok(result));
1591
+ });
1509
1592
  app.post("/api/chat/turn", async (c) => {
1510
1593
  if (!options.chatRuntime) {
1511
1594
  return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
@@ -1535,22 +1618,134 @@ function createUiRouter(options) {
1535
1618
  };
1536
1619
  try {
1537
1620
  const result = await options.chatRuntime.processTurn(request);
1538
- const completedAt = /* @__PURE__ */ new Date();
1539
- const response = {
1540
- reply: String(result.reply ?? ""),
1541
- sessionKey: readNonEmptyString(result.sessionKey) ?? sessionKey,
1542
- ...readNonEmptyString(result.agentId) || requestedAgentId ? { agentId: readNonEmptyString(result.agentId) ?? requestedAgentId } : {},
1543
- ...readNonEmptyString(result.model) || requestedModel ? { model: readNonEmptyString(result.model) ?? requestedModel } : {},
1544
- requestedAt: requestedAt.toISOString(),
1545
- completedAt: completedAt.toISOString(),
1546
- durationMs: Math.max(0, completedAt.getTime() - startedAtMs)
1547
- };
1621
+ const response = buildChatTurnView({
1622
+ result,
1623
+ fallbackSessionKey: sessionKey,
1624
+ requestedAgentId,
1625
+ requestedModel,
1626
+ requestedAt,
1627
+ startedAtMs
1628
+ });
1548
1629
  options.publish({ type: "config.updated", payload: { path: "session" } });
1549
1630
  return c.json(ok(response));
1550
1631
  } catch (error) {
1551
1632
  return c.json(err("CHAT_TURN_FAILED", String(error)), 500);
1552
1633
  }
1553
1634
  });
1635
+ app.post("/api/chat/turn/stream", async (c) => {
1636
+ const chatRuntime = options.chatRuntime;
1637
+ if (!chatRuntime) {
1638
+ return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
1639
+ }
1640
+ const body = await readJson(c.req.raw);
1641
+ if (!body.ok) {
1642
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1643
+ }
1644
+ const message = readNonEmptyString(body.data.message);
1645
+ if (!message) {
1646
+ return c.json(err("INVALID_BODY", "message is required"), 400);
1647
+ }
1648
+ const sessionKey = readNonEmptyString(body.data.sessionKey) ?? `ui:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
1649
+ const requestedAt = /* @__PURE__ */ new Date();
1650
+ const startedAtMs = requestedAt.getTime();
1651
+ const metadata = isRecord(body.data.metadata) ? body.data.metadata : void 0;
1652
+ const requestedAgentId = readNonEmptyString(body.data.agentId) ?? resolveAgentIdFromSessionKey(sessionKey);
1653
+ const requestedModel = readNonEmptyString(body.data.model);
1654
+ const request = {
1655
+ message,
1656
+ sessionKey,
1657
+ channel: readNonEmptyString(body.data.channel) ?? "ui",
1658
+ chatId: readNonEmptyString(body.data.chatId) ?? "web-ui",
1659
+ ...requestedAgentId ? { agentId: requestedAgentId } : {},
1660
+ ...requestedModel ? { model: requestedModel } : {},
1661
+ ...metadata ? { metadata } : {}
1662
+ };
1663
+ const encoder = new TextEncoder();
1664
+ const stream = new ReadableStream({
1665
+ start: async (controller) => {
1666
+ const push = (event, data) => {
1667
+ controller.enqueue(encoder.encode(toSseFrame(event, data)));
1668
+ };
1669
+ try {
1670
+ push("ready", {
1671
+ sessionKey,
1672
+ requestedAt: requestedAt.toISOString()
1673
+ });
1674
+ const streamTurn = chatRuntime.processTurnStream;
1675
+ if (!streamTurn) {
1676
+ const result = await chatRuntime.processTurn(request);
1677
+ const response = buildChatTurnView({
1678
+ result,
1679
+ fallbackSessionKey: sessionKey,
1680
+ requestedAgentId,
1681
+ requestedModel,
1682
+ requestedAt,
1683
+ startedAtMs
1684
+ });
1685
+ push("final", response);
1686
+ options.publish({ type: "config.updated", payload: { path: "session" } });
1687
+ push("done", { ok: true });
1688
+ return;
1689
+ }
1690
+ let hasFinal = false;
1691
+ for await (const event of streamTurn(request)) {
1692
+ const typed = event;
1693
+ if (typed.type === "delta") {
1694
+ if (typed.delta) {
1695
+ push("delta", { delta: typed.delta });
1696
+ }
1697
+ continue;
1698
+ }
1699
+ if (typed.type === "final") {
1700
+ const response = buildChatTurnView({
1701
+ result: typed.result,
1702
+ fallbackSessionKey: sessionKey,
1703
+ requestedAgentId,
1704
+ requestedModel,
1705
+ requestedAt,
1706
+ startedAtMs
1707
+ });
1708
+ hasFinal = true;
1709
+ push("final", response);
1710
+ options.publish({ type: "config.updated", payload: { path: "session" } });
1711
+ continue;
1712
+ }
1713
+ if (typed.type === "error") {
1714
+ push("error", {
1715
+ code: "CHAT_TURN_FAILED",
1716
+ message: typed.error
1717
+ });
1718
+ return;
1719
+ }
1720
+ }
1721
+ if (!hasFinal) {
1722
+ push("error", {
1723
+ code: "CHAT_TURN_FAILED",
1724
+ message: "stream ended without a final result"
1725
+ });
1726
+ return;
1727
+ }
1728
+ push("done", { ok: true });
1729
+ } catch (error) {
1730
+ push("error", {
1731
+ code: "CHAT_TURN_FAILED",
1732
+ message: String(error)
1733
+ });
1734
+ } finally {
1735
+ controller.close();
1736
+ }
1737
+ }
1738
+ });
1739
+ return new Response(stream, {
1740
+ status: 200,
1741
+ headers: {
1742
+ "Content-Type": "text/event-stream; charset=utf-8",
1743
+ "Cache-Control": "no-cache, no-transform",
1744
+ "Connection": "keep-alive",
1745
+ "X-Accel-Buffering": "no"
1746
+ }
1747
+ });
1748
+ });
1554
1749
  app.get("/api/sessions", (c) => {
1555
1750
  const query = c.req.query();
1556
1751
  const q = typeof query.q === "string" ? query.q : void 0;
@@ -1791,5 +1986,6 @@ export {
1791
1986
  updateChannel,
1792
1987
  updateModel,
1793
1988
  updateProvider,
1794
- updateRuntime
1989
+ updateRuntime,
1990
+ updateSecrets
1795
1991
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  "@nextclaw/openclaw-compat": "^0.1.28",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.34"
21
+ "@nextclaw/core": "^0.6.36"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",