@nekro-nxt/contracts 0.3.0 → 0.5.0

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 (37) hide show
  1. package/README.md +2 -0
  2. package/dist/index.mjs +2512 -78
  3. package/dist/index.mjs.map +1 -1
  4. package/lib/types/commands.d.ts +408 -0
  5. package/lib/types/commands.d.ts.map +1 -0
  6. package/lib/types/community-feedback-host.d.ts +442 -0
  7. package/lib/types/community-feedback-host.d.ts.map +1 -0
  8. package/lib/types/community-feedback.d.ts +1072 -0
  9. package/lib/types/community-feedback.d.ts.map +1 -0
  10. package/lib/types/community-personas.d.ts +4 -0
  11. package/lib/types/community-personas.d.ts.map +1 -1
  12. package/lib/types/community.d.ts +251 -13
  13. package/lib/types/community.d.ts.map +1 -1
  14. package/lib/types/domain.d.ts +33 -0
  15. package/lib/types/domain.d.ts.map +1 -1
  16. package/lib/types/extension-capabilities.d.ts +105 -3
  17. package/lib/types/extension-capabilities.d.ts.map +1 -1
  18. package/lib/types/extension-ui.d.ts +86 -7
  19. package/lib/types/extension-ui.d.ts.map +1 -1
  20. package/lib/types/host-api.d.ts +11109 -3874
  21. package/lib/types/host-api.d.ts.map +1 -1
  22. package/lib/types/index.d.ts +4 -0
  23. package/lib/types/index.d.ts.map +1 -1
  24. package/lib/types/logs.d.ts +355 -0
  25. package/lib/types/logs.d.ts.map +1 -0
  26. package/package.json +1 -1
  27. package/src/commands.ts +366 -0
  28. package/src/community-feedback-host.ts +121 -0
  29. package/src/community-feedback.ts +289 -0
  30. package/src/community.ts +56 -1
  31. package/src/domain.ts +59 -6
  32. package/src/extension-capabilities.ts +159 -8
  33. package/src/extension-ui.ts +40 -0
  34. package/src/host-api.ts +1540 -67
  35. package/src/index.ts +5 -0
  36. package/src/logs.ts +138 -0
  37. package/src/management-api.ts +1 -1
@@ -3,9 +3,34 @@ import { z } from 'zod'
3
3
  /**
4
4
  * Host capability level this build implements. A Revision that declares `requires.sdk` above it is rejected at
5
5
  * import with an "upgrade NekroNXT" message instead of an opaque schema error. Bump when a Manifest capability
6
- * ships; never reuse a level for a different meaning. Level 7 is Manifest V7 (host instance + agent attachments).
6
+ * ships; never reuse a level for a different meaning. Level 7 is Manifest V7 (host instance + agent attachments); level 8 adds the asset library, image info, tool result
7
+ * images, the search index and host-layer model calls. Level 9 adds the structured logger and explicit chat commands. Level 10 adds version lineage, the data version with its
8
+ * migration hook and administrator-only commands. Level 11 lists the pictures of an inbound message to inbound hooks.
7
9
  */
8
- export const EXTENSION_SDK_LEVEL = 7
10
+ export const EXTENSION_SDK_LEVEL = 11
11
+
12
+ /** Lineage, `dataVersion` and administrator-only commands need this level; packages that carry them declare it. */
13
+ export const EXTENSION_GOVERNANCE_SDK_LEVEL = 10
14
+
15
+ /**
16
+ * The first NekroNXT version of each capability level, so users read "needs NekroNXT 0.5.0" rather than a level
17
+ * number. Add the row for a new level in the change that raises EXTENSION_SDK_LEVEL; a test keeps the two in step.
18
+ */
19
+ export const EXTENSION_SDK_RELEASES: readonly { readonly level: number; readonly version: string }[] = [
20
+ { level: 6, version: '0.3.0' },
21
+ { level: 7, version: '0.4.0' },
22
+ { level: 8, version: '0.5.0' },
23
+ { level: 9, version: '0.6.0' },
24
+ { level: 10, version: '0.6.0' },
25
+ { level: 11, version: '0.6.0' },
26
+ ]
27
+
28
+ /** The oldest NekroNXT version that supports `level`; undefined for a level no release supports yet. */
29
+ export const extensionSdkVersion = (level: number): string | undefined =>
30
+ EXTENSION_SDK_RELEASES.find((release) => release.level >= level)?.version
31
+
32
+ /** Manifest V7 itself; extensions that use nothing newer need this level. */
33
+ export const EXTENSION_SDK_BASE_LEVEL = 7
9
34
 
10
35
  export const ExtensionRequiresSchema = z.object({ sdk: z.number().int().min(1).max(1000) }).strict()
11
36
  export type ExtensionRequires = z.output<typeof ExtensionRequiresSchema>
@@ -150,6 +175,86 @@ export const ExtensionPlatformCapabilitySchema = z
150
175
  .strict()
151
176
  export type ExtensionPlatformCapability = z.output<typeof ExtensionPlatformCapabilitySchema>
152
177
 
178
+ export const EXTENSION_ASSET_LIBRARY_DEFAULT_QUOTA_BYTES = 2 * 1024 * 1024 * 1024
179
+ export const EXTENSION_ASSET_LIBRARY_MAX_QUOTA_BYTES = 32 * 1024 * 1024 * 1024
180
+
181
+ export const ExtensionAssetLibraryCapabilitySchema = z
182
+ .object({
183
+ quotaBytes: z
184
+ .number()
185
+ .int()
186
+ .min(1024 * 1024)
187
+ .max(EXTENSION_ASSET_LIBRARY_MAX_QUOTA_BYTES)
188
+ .optional(),
189
+ })
190
+ .strict()
191
+ export type ExtensionAssetLibraryCapability = z.output<typeof ExtensionAssetLibraryCapabilitySchema>
192
+
193
+ /**
194
+ * `write` creates Assets in the current channel; `library` keeps Assets in the extension's own library so they can be
195
+ * used in any channel later.
196
+ */
197
+ export const ExtensionAssetsCapabilitySchema = z
198
+ .object({
199
+ write: z.literal(true).optional(),
200
+ library: ExtensionAssetLibraryCapabilitySchema.optional(),
201
+ })
202
+ .strict()
203
+ .refine((value) => value.write !== undefined || value.library !== undefined, 'assets 至少声明 write 或 library。')
204
+ export type ExtensionAssetsCapability = z.output<typeof ExtensionAssetsCapabilitySchema>
205
+
206
+ const IndexNameSchema = z
207
+ .string()
208
+ .trim()
209
+ .regex(/^[a-z][a-z0-9_]{0,31}$/u, '索引、字段和过滤项名称只能使用小写字母、数字和下划线,以字母开头。')
210
+
211
+ export const EXTENSION_INDEX_MAX_TEXT_FIELDS = 4
212
+ export const EXTENSION_INDEX_MAX_FILTERS = 4
213
+
214
+ /**
215
+ * One searchable collection. Text fields are matched by keywords (and vectors when the Host has them enabled); weights
216
+ * rank keyword matches. Filter fields hold short keyword values, possibly several per document.
217
+ */
218
+ export const ExtensionIndexCollectionSchema = z
219
+ .object({
220
+ name: IndexNameSchema,
221
+ fields: z
222
+ .array(z.object({ name: IndexNameSchema, weight: z.number().min(0.1).max(10).default(1) }).strict())
223
+ .min(1)
224
+ .max(EXTENSION_INDEX_MAX_TEXT_FIELDS),
225
+ filters: z.array(IndexNameSchema).max(EXTENSION_INDEX_MAX_FILTERS).default([]),
226
+ })
227
+ .strict()
228
+ .superRefine((value, context) => {
229
+ const names = [...value.fields.map(({ name }) => name), ...value.filters]
230
+ if (new Set(names).size !== names.length) {
231
+ context.addIssue({ code: 'custom', message: '同一索引里的字段与过滤项不能重名。' })
232
+ }
233
+ })
234
+ export type ExtensionIndexCollection = z.output<typeof ExtensionIndexCollectionSchema>
235
+
236
+ export const ExtensionIndexCapabilitySchema = z
237
+ .object({
238
+ collections: z
239
+ .array(ExtensionIndexCollectionSchema)
240
+ .min(1)
241
+ .max(4)
242
+ .refine((value) => new Set(value.map(({ name }) => name)).size === value.length, '索引名称不能重复。'),
243
+ })
244
+ .strict()
245
+ export type ExtensionIndexCapability = z.output<typeof ExtensionIndexCapabilitySchema>
246
+
247
+ export const EXTENSION_MODEL_MAX_CALLS_PER_MINUTE = 120
248
+
249
+ /** Calls to a model the user picks from the Host's configured models, with images allowed; host layer only. */
250
+ export const ExtensionModelsCapabilitySchema = z
251
+ .object({
252
+ maxCallsPerMinute: z.number().int().min(1).max(EXTENSION_MODEL_MAX_CALLS_PER_MINUTE),
253
+ maxOutputTokens: z.number().int().min(64).max(EXTENSION_LLM_MAX_OUTPUT_TOKENS),
254
+ })
255
+ .strict()
256
+ export type ExtensionModelsCapability = z.output<typeof ExtensionModelsCapabilitySchema>
257
+
153
258
  /** A header or environment value: literal text, or the current value of a `meta.role: 'secret'` config field. */
154
259
  export const ExtensionMcpValueSchema = z.union([
155
260
  z.string().max(4096),
@@ -259,10 +364,10 @@ export const ExtensionCapabilitiesSchema = z
259
364
  .object({
260
365
  network: ExtensionNetworkCapabilitySchema.optional(),
261
366
  storage: ExtensionStorageCapabilitySchema.optional(),
262
- assets: z
263
- .object({ write: z.literal(true) })
264
- .strict()
265
- .optional(),
367
+ assets: ExtensionAssetsCapabilitySchema.optional(),
368
+ index: ExtensionIndexCapabilitySchema.optional(),
369
+ /** Host layer only; the agent layer uses `llm`, which runs on the agent's own model. */
370
+ models: ExtensionModelsCapabilitySchema.optional(),
266
371
  history: z
267
372
  .object({ read: z.literal(true) })
268
373
  .strict()
@@ -292,6 +397,11 @@ export const HostLayerCapabilitiesSchema = z
292
397
  .object({ quotaBytes: z.number().int().min(1024).max(EXTENSION_STORAGE_MAX_QUOTA_BYTES).optional() })
293
398
  .strict()
294
399
  .optional(),
400
+ /** The same library agent attachments use; the host instance lists and imports into it. */
401
+ assets: z.object({ library: ExtensionAssetLibraryCapabilitySchema }).strict().optional(),
402
+ /** The same collections agent attachments declare; pages search and maintain them. */
403
+ index: ExtensionIndexCapabilitySchema.optional(),
404
+ models: ExtensionModelsCapabilitySchema.optional(),
295
405
  })
296
406
  .strict()
297
407
  export type HostLayerCapabilities = z.output<typeof HostLayerCapabilitiesSchema>
@@ -310,6 +420,9 @@ export const hostLayerAsCapabilities = (host: HostLayerCapabilities | undefined)
310
420
  ...(host.storage.quotaBytes === undefined ? {} : { quotaBytes: host.storage.quotaBytes }),
311
421
  },
312
422
  }),
423
+ ...(host.assets === undefined ? {} : { assets: { library: host.assets.library } }),
424
+ ...(host.index === undefined ? {} : { index: host.index }),
425
+ ...(host.models === undefined ? {} : { models: host.models }),
313
426
  }
314
427
 
315
428
  const NETWORK_MODE_RANK = { domains: 0, config: 1, unrestricted: 2 } as const
@@ -344,7 +457,16 @@ export const extensionCapabilitiesExpand = (
344
457
  if (next === undefined) return false
345
458
  if (networkExpands(previous?.network, next.network)) return true
346
459
  if (next.storage?.scopes.some((scope) => !(previous?.storage?.scopes ?? []).includes(scope))) return true
347
- if (next.assets !== undefined && previous?.assets === undefined) return true
460
+ if (next.assets?.write !== undefined && previous?.assets?.write === undefined) return true
461
+ if (next.assets?.library !== undefined && previous?.assets?.library === undefined) return true
462
+ if (
463
+ next.models !== undefined &&
464
+ (previous?.models === undefined ||
465
+ next.models.maxCallsPerMinute > previous.models.maxCallsPerMinute ||
466
+ next.models.maxOutputTokens > previous.models.maxOutputTokens)
467
+ ) {
468
+ return true
469
+ }
348
470
  if (next.history !== undefined && previous?.history === undefined) return true
349
471
  const platformKey = ({ adapter, action }: { readonly adapter: string; readonly action: string }) =>
350
472
  `${adapter}:${action}`
@@ -381,6 +503,11 @@ export const extensionCapabilitiesExpand = (
381
503
  return false
382
504
  }
383
505
 
506
+ const formatLibraryQuota = (bytes: number): string =>
507
+ bytes >= 1024 * 1024 * 1024
508
+ ? `${+(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`
509
+ : `${Math.round(bytes / 1024 / 1024)} MB`
510
+
384
511
  /** Capability risk tiers shown on the approval page (§7 of the capability decision). */
385
512
  export type ExtensionCapabilityRisk = 'normal' | 'sensitive' | 'high'
386
513
 
@@ -428,8 +555,32 @@ export const summarizeExtensionCapabilities = (
428
555
  label: shared ? '保存数据,并在启用它的智能体之间共享' : '为这个智能体保存数据',
429
556
  })
430
557
  }
431
- if (capabilities.assets !== undefined)
558
+ if (capabilities.assets?.write !== undefined)
432
559
  items.push({ key: 'assets', risk: 'normal', label: '在当前频道生成图片或文件' })
560
+ if (capabilities.assets?.library !== undefined) {
561
+ items.push({
562
+ key: 'assets.library',
563
+ risk: 'sensitive',
564
+ label: '把频道里的图片收进扩展资源库,之后可以在其他频道使用',
565
+ detail: `最多 ${formatLibraryQuota(capabilities.assets.library.quotaBytes ?? EXTENSION_ASSET_LIBRARY_DEFAULT_QUOTA_BYTES)}`,
566
+ })
567
+ }
568
+ if (capabilities.index !== undefined) {
569
+ items.push({
570
+ key: 'index',
571
+ risk: 'normal',
572
+ label: '建立可搜索的索引',
573
+ detail: capabilities.index.collections.map(({ name }) => name).join('、'),
574
+ })
575
+ }
576
+ if (capabilities.models !== undefined) {
577
+ items.push({
578
+ key: 'models',
579
+ risk: 'sensitive',
580
+ label: '使用本机已配置的模型,可以发送图片',
581
+ detail: `每分钟最多 ${capabilities.models.maxCallsPerMinute} 次,计入所选模型的用量`,
582
+ })
583
+ }
433
584
  if (capabilities.history !== undefined)
434
585
  items.push({ key: 'history', risk: 'normal', label: '读取当前频道的聊天记录' })
435
586
  const hook = capabilities.inboundHook
@@ -3,6 +3,7 @@ import {
3
3
  ExtensionCapabilitiesSchema,
4
4
  HostLayerCapabilitiesSchema,
5
5
  hostLayerAsCapabilities,
6
+ EXTENSION_SDK_BASE_LEVEL,
6
7
  } from './extension-capabilities.js'
7
8
  import { ExtensionIdSchema, ExtensionRevisionIdSchema, HostUiPageInstanceIdSchema } from './domain.js'
8
9
  import { ConfigSchemaDocumentSchema } from './config-schema.js'
@@ -252,6 +253,26 @@ export const ExtensionPermissionsSchema = z
252
253
  })
253
254
  .strict()
254
255
  .superRefine(checkBrowserPermissions)
256
+ .superRefine((value, context) => {
257
+ if (value.agent?.models !== undefined) {
258
+ context.addIssue({
259
+ code: 'custom',
260
+ path: ['agent', 'models'],
261
+ message: 'models 只能在 permissions.host 中声明;智能体层使用 llm 调用智能体自己的模型。',
262
+ })
263
+ }
264
+ // Both layers read and write the same collections, so a name must mean one definition.
265
+ for (const collection of value.agent?.index?.collections ?? []) {
266
+ const host = value.host?.index?.collections.find(({ name }) => name === collection.name)
267
+ if (host !== undefined && JSON.stringify(host) !== JSON.stringify(collection)) {
268
+ context.addIssue({
269
+ code: 'custom',
270
+ path: ['agent', 'index'],
271
+ message: `索引 ${collection.name} 在 permissions.host 与 permissions.agent 中的定义不一致。`,
272
+ })
273
+ }
274
+ }
275
+ })
255
276
 
256
277
  export type ExtensionPermissions = z.output<typeof ExtensionPermissionsSchema>
257
278
 
@@ -518,3 +539,22 @@ export const EXTENSION_DATA_HOOK_PERMISSIONS = {
518
539
  } as const satisfies Readonly<Record<string, HostUiPermission>>
519
540
 
520
541
  export type ExtensionDataHookName = keyof typeof EXTENSION_DATA_HOOK_PERMISSIONS
542
+
543
+ /**
544
+ * The capability level a set of permissions needs: Manifest V7 itself, or the level that introduced the newest
545
+ * capability it declares. Packaging and import compare it with `requires.sdk`, so an extension cannot claim a level
546
+ * too old for what it uses.
547
+ */
548
+ export const requiredExtensionSdkLevel = (
549
+ permissions: ExtensionPermissions | undefined,
550
+ ): { readonly level: number; readonly capabilities: readonly string[] } => {
551
+ const level8 = [
552
+ permissions?.host?.assets === undefined ? undefined : '扩展资源库',
553
+ permissions?.agent?.assets?.library === undefined ? undefined : '扩展资源库',
554
+ permissions?.host?.index === undefined && permissions?.agent?.index === undefined ? undefined : '检索索引',
555
+ permissions?.host?.models === undefined ? undefined : '本机层模型调用',
556
+ ].filter((name): name is string => name !== undefined)
557
+ return level8.length > 0
558
+ ? { level: 8, capabilities: [...new Set(level8)] }
559
+ : { level: EXTENSION_SDK_BASE_LEVEL, capabilities: [] }
560
+ }