@nekro-nxt/contracts 0.0.0-stage → 0.1.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.
@@ -0,0 +1,3383 @@
1
+ import { z } from 'zod'
2
+ import { McpServerFormSchema } from './extension-capabilities.js'
3
+ import { HostUpgradeSummarySchema, RuntimeCompatibilityDiagnosticSchema } from './runtime-compatibility.js'
4
+ import {
5
+ AgentIdSchema,
6
+ AgentRevisionIdSchema,
7
+ AuthoringAttemptIdSchema,
8
+ AuthoringTaskIdSchema,
9
+ AssetIdSchema,
10
+ ChannelEventIdSchema,
11
+ AdapterActivityKeySchema,
12
+ ChannelIdSchema,
13
+ ChannelMemberIdSchema,
14
+ ConnectionIdSchema,
15
+ ConnectionEventIdSchema,
16
+ DshPluginEntryIdSchema,
17
+ DshPluginPackageIdSchema,
18
+ ExtensionIdSchema,
19
+ ExtensionRevisionIdSchema,
20
+ HostUiPageInstanceIdSchema,
21
+ EpisodeIdSchema,
22
+ JsonValueSchema,
23
+ LogicalMessageIdSchema,
24
+ RichExtensionSchema,
25
+ RichTargetUrlSchema,
26
+ OutboundIntentIdSchema,
27
+ PlatformIdentityIdSchema,
28
+ PromptDocumentV1Schema,
29
+ promptDocumentPlainText,
30
+ } from './domain.js'
31
+ import { ClientNotificationSchema, ManagementDeviceIdSchema, ManagementDeviceViewSchema } from './management-api.js'
32
+ import { ConfigSchemaDocumentSchema } from './config-schema.js'
33
+ import {
34
+ CommunityExtensionDetailSchema,
35
+ CommunityExtensionSummarySchema,
36
+ CommunityReleaseIdSchema,
37
+ CommunityReviewStatusSchema,
38
+ CommunityStatusSchema,
39
+ } from './community.js'
40
+ import {
41
+ DshNxtHostUiSchema,
42
+ EMPTY_EXTENSION_UI_CONTRIBUTIONS,
43
+ ExtensionUiContributionsSchema,
44
+ PanelContributionSchema,
45
+ RichKindSchema,
46
+ HostPageContributionSchema,
47
+ HostIconNameSchema,
48
+ HostUiKitComponentNameSchema,
49
+ HostUiPageGeometryEvidenceSchema,
50
+ HostUiPageEntrySchema,
51
+ HostUiPermissionDeclarationSchema,
52
+ } from './extension-ui.js'
53
+
54
+ const EmptyParamsSchema = z.object({}).strict()
55
+ const NoRequestBodySchema = z.undefined()
56
+ const NonEmptyStringSchema = z.string().trim().min(1)
57
+ const DynamicIdSchema = NonEmptyStringSchema
58
+
59
+ const DynamicAttemptStatusSchema = z.enum([
60
+ 'awaiting-approval',
61
+ 'starting-host',
62
+ 'client-pending',
63
+ 'running',
64
+ 'waiting',
65
+ 'rejected',
66
+ 'failed',
67
+ 'cancelled',
68
+ 'stopped',
69
+ ])
70
+
71
+ const DynamicHalfStateSchema = z
72
+ .object({
73
+ status: z.enum(['absent', 'pending', 'stopped', 'running', 'waiting', 'failed']),
74
+ waitingFor: z.array(z.string()),
75
+ error: z.string().optional(),
76
+ })
77
+ .strict()
78
+
79
+ const DynamicAttemptErrorSchema = z
80
+ .object({
81
+ phase: z.enum(['approval', 'host-load', 'host-apply', 'client-load', 'client-apply', 'client-render']),
82
+ message: z.string(),
83
+ stack: z.string().optional(),
84
+ pluginId: DynamicIdSchema,
85
+ packageId: DynamicIdSchema,
86
+ pluginRunId: DynamicIdSchema,
87
+ })
88
+ .strict()
89
+
90
+ const DynamicLatestRunSchema = z
91
+ .object({
92
+ pluginRunId: DynamicIdSchema,
93
+ packageId: DynamicIdSchema,
94
+ mode: z.enum(['run', 'update']),
95
+ status: DynamicAttemptStatusSchema,
96
+ approvalRequestId: DynamicIdSchema.optional(),
97
+ requiresApproval: z.boolean().optional(),
98
+ host: DynamicHalfStateSchema,
99
+ client: DynamicHalfStateSchema,
100
+ error: DynamicAttemptErrorSchema.optional(),
101
+ })
102
+ .strict()
103
+
104
+ const AuthoringTaskStatusSchema = z.enum([
105
+ 'working',
106
+ 'awaiting-approval',
107
+ 'running',
108
+ 'ready',
109
+ 'repairing',
110
+ 'failed',
111
+ 'interrupted',
112
+ 'stopped',
113
+ 'completed',
114
+ ])
115
+
116
+ const AuthoringAttemptStateSchema = z.enum([
117
+ 'drafting',
118
+ 'preflight-failed',
119
+ 'awaiting-approval',
120
+ 'starting-host',
121
+ 'loading-client',
122
+ 'verifying',
123
+ 'active',
124
+ 'failed',
125
+ 'rejected',
126
+ 'stopped',
127
+ ])
128
+
129
+ const AuthoringAttemptSummarySchema = z
130
+ .object({
131
+ id: AuthoringAttemptIdSchema,
132
+ ordinal: z.number().int().positive(),
133
+ name: z.string(),
134
+ purpose: z.string(),
135
+ state: AuthoringAttemptStateSchema,
136
+ riskDigest: z.string().regex(/^[a-f0-9]{64}$/u),
137
+ host: DynamicHalfStateSchema,
138
+ client: DynamicHalfStateSchema,
139
+ error: z
140
+ .object({
141
+ phase: z.enum([
142
+ 'preflight',
143
+ 'approval',
144
+ 'host-load',
145
+ 'host-apply',
146
+ 'client-load',
147
+ 'client-apply',
148
+ 'client-render',
149
+ 'verification',
150
+ 'settlement',
151
+ 'restore',
152
+ ]),
153
+ message: z.string(),
154
+ repairable: z.boolean(),
155
+ })
156
+ .strict()
157
+ .optional(),
158
+ createdAt: z.number().int().nonnegative(),
159
+ settledAt: z.number().int().nonnegative().optional(),
160
+ })
161
+ .strict()
162
+
163
+ const AuthoringTaskSummarySchema = z
164
+ .object({
165
+ id: AuthoringTaskIdSchema,
166
+ agentId: AgentIdSchema,
167
+ channelId: ChannelIdSchema,
168
+ episodeId: EpisodeIdSchema,
169
+ title: z.string(),
170
+ requirementSummary: z.string(),
171
+ status: AuthoringTaskStatusSchema,
172
+ approvalPolicy: z.enum(['risk-stable', 'fully-automatic']),
173
+ approvedRiskDigest: z
174
+ .string()
175
+ .regex(/^[a-f0-9]{64}$/u)
176
+ .optional(),
177
+ revision: z.number().int().positive(),
178
+ activeAttempt: AuthoringAttemptSummarySchema.optional(),
179
+ candidateAttempt: AuthoringAttemptSummarySchema.optional(),
180
+ /** Latest attempt that still carries product verification evidence; it can be restored as the candidate. */
181
+ verifiedAttempt: AuthoringAttemptSummarySchema.optional(),
182
+ createdAt: z.number().int().nonnegative(),
183
+ updatedAt: z.number().int().nonnegative(),
184
+ })
185
+ .strict()
186
+ const ConnectionAliasInputSchema = z
187
+ .string()
188
+ .trim()
189
+ .max(80)
190
+ .transform((value) => value || undefined)
191
+ const ConnectionAliasOutputSchema = z.string().trim().min(1).max(80)
192
+
193
+ export const HostApiErrorSchema = z
194
+ .object({
195
+ error: z
196
+ .object({
197
+ code: NonEmptyStringSchema,
198
+ message: z.string(),
199
+ })
200
+ .strict(),
201
+ })
202
+ .strict()
203
+
204
+ const AgentModelSchema = z
205
+ .object({
206
+ provider: NonEmptyStringSchema,
207
+ model: NonEmptyStringSchema,
208
+ reasoningEffort: NonEmptyStringSchema.optional(),
209
+ })
210
+ .strict()
211
+
212
+ export const ImageUnderstandingPolicyApiSchema = z
213
+ .object({
214
+ history: z
215
+ .object({
216
+ mode: z.literal('persistent-distinct'),
217
+ detail: z.enum(['low', 'auto', 'high']),
218
+ restoreAfterCompaction: z
219
+ .object({
220
+ recentMessages: z.number().int().min(1).max(100),
221
+ maxImages: z.number().int().min(1).max(50),
222
+ })
223
+ .strict(),
224
+ })
225
+ .strict(),
226
+ textModel: z.discriminatedUnion('mode', [
227
+ z.object({ mode: z.literal('disabled') }).strict(),
228
+ z
229
+ .object({
230
+ mode: z.literal('auxiliary'),
231
+ model: AgentModelSchema,
232
+ maxTokens: z.number().int().min(256).max(8192),
233
+ })
234
+ .strict(),
235
+ ]),
236
+ })
237
+ .strict()
238
+
239
+ const AgentCapabilitiesSchema = z
240
+ .object({
241
+ subagents: z.boolean(),
242
+ fileTools: z.boolean(),
243
+ webSearch: z.boolean(),
244
+ dynamicCreation: z.boolean(),
245
+ developmentShell: z.boolean(),
246
+ unrestrictedFileAccess: z.boolean(),
247
+ /** Absent in older clients means on. */
248
+ scheduledTasks: z.boolean().default(true),
249
+ })
250
+ .strict()
251
+
252
+ export const DynamicClientApprovalPolicySchema = z.enum(['manual', 'automatic'])
253
+
254
+ export const NotificationEventKeySchema = z.enum(['dynamic-client-approval-requested'])
255
+ export type NotificationEventKey = z.output<typeof NotificationEventKeySchema>
256
+
257
+ export const NotificationSettingsViewSchema = z
258
+ .object({
259
+ revision: z.number().int().positive().optional(),
260
+ system: z.object({ enabled: z.boolean() }).strict(),
261
+ bark: z
262
+ .object({
263
+ enabled: z.boolean(),
264
+ serverUrl: z.url(),
265
+ deviceKeyConfigured: z.boolean(),
266
+ })
267
+ .strict(),
268
+ events: z.record(NotificationEventKeySchema, z.boolean()),
269
+ })
270
+ .strict()
271
+
272
+ export const UpdateNotificationSettingsRequestSchema = z
273
+ .object({
274
+ expectedRevision: z.number().int().positive().optional(),
275
+ system: z.object({ enabled: z.boolean() }).strict(),
276
+ bark: z
277
+ .object({
278
+ enabled: z.boolean(),
279
+ serverUrl: z.url(),
280
+ deviceKey: z.string().trim().min(1).max(2048).optional(),
281
+ clearDeviceKey: z.boolean().optional(),
282
+ })
283
+ .strict(),
284
+ events: z.record(NotificationEventKeySchema, z.boolean()),
285
+ })
286
+ .strict()
287
+ .refine((value) => !(value.bark.deviceKey !== undefined && value.bark.clearDeviceKey === true), {
288
+ message: '不能同时更新并清除 Bark Device Key。',
289
+ path: ['bark'],
290
+ })
291
+
292
+ export const TestBarkNotificationRequestSchema = z
293
+ .object({ serverUrl: z.url(), deviceKey: z.string().trim().min(1).max(2048).optional() })
294
+ .strict()
295
+
296
+ export const ClientNotificationFeedResponseSchema = z
297
+ .object({
298
+ cursor: z.number().int().nonnegative(),
299
+ notifications: z.array(ClientNotificationSchema),
300
+ })
301
+ .strict()
302
+
303
+ const AgentRevisionRequestContentSchema = z
304
+ .object({
305
+ displayName: z.string().trim().min(1).max(80),
306
+ persona: z
307
+ .string()
308
+ .max(64 * 1024)
309
+ .default(''),
310
+ personaDocument: PromptDocumentV1Schema.optional(),
311
+ model: AgentModelSchema,
312
+ imagePolicy: ImageUnderstandingPolicyApiSchema.optional(),
313
+ dynamicClientApprovalPolicy: DynamicClientApprovalPolicySchema.optional(),
314
+ })
315
+ .strict()
316
+
317
+ const validatePersonaDocumentProjection = (
318
+ value: z.output<typeof AgentRevisionRequestContentSchema>,
319
+ context: z.core.$RefinementCtx<z.output<typeof AgentRevisionRequestContentSchema>>,
320
+ ): void => {
321
+ if (value.personaDocument !== undefined && promptDocumentPlainText(value.personaDocument) !== value.persona) {
322
+ context.addIssue({
323
+ code: 'custom',
324
+ message: '人设文本必须与结构化人设文档一致。',
325
+ path: ['personaDocument'],
326
+ input: value,
327
+ })
328
+ }
329
+ }
330
+
331
+ const CreateAgentRequestSchema = AgentRevisionRequestContentSchema.extend({
332
+ capabilities: AgentCapabilitiesSchema.optional(),
333
+ }).superRefine((value, context) => {
334
+ validatePersonaDocumentProjection(value, context)
335
+ })
336
+
337
+ const ReviseAgentRequestSchema = AgentRevisionRequestContentSchema.extend({
338
+ expectedCurrentRevisionId: AgentRevisionIdSchema,
339
+ }).superRefine((value, context) => {
340
+ validatePersonaDocumentProjection(value, context)
341
+ })
342
+
343
+ // The default that fills `scheduledTasks` for older clients must not turn it on in a partial update.
344
+ const UpdateAgentCapabilitiesRequestSchema = AgentCapabilitiesSchema.partial()
345
+ .extend({ scheduledTasks: z.boolean().optional() })
346
+ .strict()
347
+ .refine((value) => Object.values(value).some((entry) => entry !== undefined), '至少提供一个能力。')
348
+
349
+ const TriggerPolicySchema = z.enum(['always', 'mentioned-or-replied', 'command', 'observe-only'])
350
+
351
+ export const WorkTreeOrderSchema = z
352
+ .object({
353
+ agentIds: z.array(AgentIdSchema),
354
+ channelIdsByAgent: z.record(AgentIdSchema, z.array(ChannelIdSchema)),
355
+ unboundChannelIds: z.array(ChannelIdSchema),
356
+ })
357
+ .strict()
358
+
359
+ export type WorkTreeOrder = z.output<typeof WorkTreeOrderSchema>
360
+
361
+ const SnapshotMessagePartSchema = z.discriminatedUnion('type', [
362
+ z.object({ type: z.literal('text'), text: z.string() }).strict(),
363
+ z
364
+ .object({ type: z.literal('mention'), memberId: ChannelMemberIdSchema, displayName: z.string().optional() })
365
+ .strict(),
366
+ z.object({ type: z.literal('image'), assetId: AssetIdSchema, alt: z.string().optional() }).strict(),
367
+ z.object({ type: z.literal('file'), assetId: AssetIdSchema, name: z.string().optional() }).strict(),
368
+ z.object({ type: z.literal('audio'), assetId: AssetIdSchema }).strict(),
369
+ z.object({ type: z.literal('quote'), messageId: LogicalMessageIdSchema }).strict(),
370
+ z
371
+ .object({
372
+ type: z.literal('rich'),
373
+ adapterKey: z.string().trim().min(1).max(64),
374
+ kind: z.string().trim().min(1).max(64),
375
+ summary: z.string().trim().min(1).max(500),
376
+ title: z.string().trim().min(1).max(200).optional(),
377
+ source: z.string().trim().min(1).max(80).optional(),
378
+ targetUrl: RichTargetUrlSchema.optional(),
379
+ previewAssetId: AssetIdSchema.optional(),
380
+ extension: RichExtensionSchema.optional(),
381
+ })
382
+ .strict(),
383
+ ])
384
+
385
+ export const HostSnapshotMessageSchema = z
386
+ .object({
387
+ id: z.union([ChannelEventIdSchema, OutboundIntentIdSchema]),
388
+ channelId: ChannelIdSchema,
389
+ role: z.enum(['member', 'agent', 'system']),
390
+ parts: z.array(SnapshotMessagePartSchema),
391
+ sender: z.object({ memberId: ChannelMemberIdSchema, displayName: z.string().optional() }).strict().optional(),
392
+ mentionedConnectionAccount: z.boolean().optional(),
393
+ activityKey: AdapterActivityKeySchema.optional(),
394
+ targetLogicalMessageId: LogicalMessageIdSchema.optional(),
395
+ occurredAt: z.number().finite(),
396
+ deliveryState: z.enum(['planned', 'sending', 'sent', 'partially-sent', 'failed', 'unknown']).optional(),
397
+ /** Latest administrator resolution of an unsettled delivery; history keeps the original receipt state. */
398
+ deliveryResolution: z
399
+ .object({
400
+ action: z.enum(['retry', 'confirm-delivered']),
401
+ resolvedAt: z.number().int().safe().nonnegative(),
402
+ })
403
+ .strict()
404
+ .optional(),
405
+ origin: z.enum(['admin-console']).optional(),
406
+ /** What the agent's inbound hooks decided for this message, when anything differs from the default. */
407
+ inboundHook: z
408
+ .object({
409
+ trigger: z.enum(['default', 'suppress', 'force']),
410
+ hidden: z.boolean(),
411
+ annotation: z.string().optional(),
412
+ extensions: z.array(z.string()),
413
+ problems: z.array(z.string()).optional(),
414
+ })
415
+ .strict()
416
+ .optional(),
417
+ /** A scheduled task came due: a Host fact shown as a notice, not a member message. */
418
+ scheduledTask: z
419
+ .object({ label: z.string(), extensionName: z.string().optional(), note: z.string().optional() })
420
+ .strict()
421
+ .optional(),
422
+ })
423
+ .strict()
424
+
425
+ export type HostSnapshotMessage = z.output<typeof HostSnapshotMessageSchema>
426
+
427
+ export const HostConnectionEventSchema = z
428
+ .object({
429
+ id: ConnectionEventIdSchema,
430
+ connectionId: ConnectionIdSchema,
431
+ activityKey: AdapterActivityKeySchema,
432
+ summary: z.string(),
433
+ actor: z.object({ identityId: PlatformIdentityIdSchema, displayName: z.string().optional() }).strict().optional(),
434
+ subject: z.object({ identityId: PlatformIdentityIdSchema, displayName: z.string().optional() }).strict().optional(),
435
+ occurredAt: z.number().int().safe().nonnegative(),
436
+ })
437
+ .strict()
438
+
439
+ export type HostConnectionEvent = z.output<typeof HostConnectionEventSchema>
440
+
441
+ const PlatformUserChannelPreviewSchema = z
442
+ .object({
443
+ id: ChannelIdSchema,
444
+ displayName: z.string().trim().min(1).max(120).optional(),
445
+ kind: z.enum(['internal', 'direct', 'group']),
446
+ })
447
+ .strict()
448
+
449
+ const PlatformUserFacetAdapterSchema = z
450
+ .object({ key: NonEmptyStringSchema, displayName: NonEmptyStringSchema, userCount: z.number().int().nonnegative() })
451
+ .strict()
452
+
453
+ const PlatformUserFacetConnectionSchema = z
454
+ .object({
455
+ id: ConnectionIdSchema,
456
+ adapterKey: NonEmptyStringSchema,
457
+ displayName: NonEmptyStringSchema,
458
+ userCount: z.number().int().nonnegative(),
459
+ })
460
+ .strict()
461
+
462
+ export const PlatformUserListResponseSchema = z
463
+ .object({
464
+ total: z.number().int().nonnegative(),
465
+ items: z.array(
466
+ z
467
+ .object({
468
+ identityId: PlatformIdentityIdSchema,
469
+ displayName: z.string().trim().min(1).max(120).optional(),
470
+ adapter: z.object({ key: NonEmptyStringSchema, displayName: NonEmptyStringSchema }).strict(),
471
+ connection: z.object({ id: ConnectionIdSchema, displayName: NonEmptyStringSchema }).strict(),
472
+ activeChannelCount: z.number().int().nonnegative(),
473
+ channelPreview: z.array(PlatformUserChannelPreviewSchema).max(3),
474
+ historicalOnly: z.boolean(),
475
+ })
476
+ .strict(),
477
+ ),
478
+ facets: z
479
+ .object({
480
+ adapters: z.array(PlatformUserFacetAdapterSchema),
481
+ connections: z.array(PlatformUserFacetConnectionSchema),
482
+ })
483
+ .strict(),
484
+ nextCursor: PlatformIdentityIdSchema.optional(),
485
+ })
486
+ .strict()
487
+
488
+ export type PlatformUserListResponse = z.output<typeof PlatformUserListResponseSchema>
489
+
490
+ export const ChannelFactSseItemSchema = z
491
+ .object({
492
+ kind: z.enum(['inbound', 'outbound']),
493
+ sourceId: z.union([ChannelEventIdSchema, OutboundIntentIdSchema]),
494
+ message: HostSnapshotMessageSchema,
495
+ })
496
+ .strict()
497
+
498
+ export const ChannelFactSseDataSchema = z
499
+ .object({
500
+ channelId: ChannelIdSchema,
501
+ revision: z.number().int().positive(),
502
+ items: z.array(ChannelFactSseItemSchema).min(1),
503
+ })
504
+ .strict()
505
+
506
+ export const ChannelRuntimePhaseSchema = z.enum(['idle', 'thinking', 'using-tool', 'waiting-input', 'unavailable'])
507
+
508
+ export const ChannelRuntimeUsageSchema = z
509
+ .object({
510
+ inputTokens: z.number().int().nonnegative(),
511
+ outputTokens: z.number().int().nonnegative(),
512
+ totalTokens: z.number().int().nonnegative().optional(),
513
+ cacheReadTokens: z.number().int().nonnegative().optional(),
514
+ cacheWriteTokens: z.number().int().nonnegative().optional(),
515
+ reasoningTokens: z.number().int().nonnegative().optional(),
516
+ })
517
+ .strict()
518
+
519
+ export const ChannelRuntimeToolSchema = z
520
+ .object({
521
+ callId: NonEmptyStringSchema,
522
+ name: NonEmptyStringSchema,
523
+ displayName: NonEmptyStringSchema,
524
+ state: z.enum(['running', 'succeeded', 'failed']),
525
+ inputPreview: z.string().optional(),
526
+ resultPreview: z.string().optional(),
527
+ wroteToChannel: z.boolean().optional(),
528
+ deliveryState: z.enum(['sent', 'partially-sent', 'failed', 'unknown']).optional(),
529
+ durationMs: z.number().int().nonnegative().optional(),
530
+ })
531
+ .strict()
532
+
533
+ export const ChannelRuntimeStepSchema = z
534
+ .object({
535
+ step: z.number().int().nonnegative(),
536
+ tools: z.array(ChannelRuntimeToolSchema),
537
+ internalOutput: z
538
+ .object({
539
+ kind: z.literal('internal-output'),
540
+ text: z.string().optional(),
541
+ reasoning: z.string().optional(),
542
+ })
543
+ .strict()
544
+ .optional(),
545
+ durationMs: z.number().int().nonnegative().optional(),
546
+ firstTokenMs: z.number().int().nonnegative().optional(),
547
+ usage: ChannelRuntimeUsageSchema.optional(),
548
+ })
549
+ .strict()
550
+
551
+ export const ChannelRuntimeTurnSchema = z
552
+ .object({
553
+ turn: z.number().int().nonnegative(),
554
+ state: z.enum(['in-progress', 'completed', 'unreplied', 'aborted', 'error', 'max-tokens', 'interrupted']),
555
+ producedReply: z.boolean(),
556
+ responseState: z.enum(['not-required', 'pending', 'sent', 'finished', 'protocol-failed', 'interrupted']),
557
+ error: z.object({ code: NonEmptyStringSchema, message: z.string() }).strict().optional(),
558
+ steps: z.array(ChannelRuntimeStepSchema),
559
+ durationMs: z.number().int().nonnegative().optional(),
560
+ /** Session log time the turn opened. */
561
+ startedAt: z.number().int().safe().nonnegative().optional(),
562
+ /** Session log time the turn closed; absent while it is still running. */
563
+ endedAt: z.number().int().safe().nonnegative().optional(),
564
+ /** Newest Channel Event of the admission that opened this turn; absent for non-channel turns. */
565
+ triggerEventId: ChannelEventIdSchema.optional(),
566
+ })
567
+ .strict()
568
+
569
+ export const ChannelRuntimeOccupancySchema = z
570
+ .object({
571
+ projectedTokens: z.number().int().nonnegative(),
572
+ contextWindow: z.number().int().positive(),
573
+ breakdown: z
574
+ .object({
575
+ systemTokens: z.number().int().nonnegative(),
576
+ toolsTokens: z.number().int().nonnegative(),
577
+ messageTokens: z.number().int().nonnegative(),
578
+ })
579
+ .strict()
580
+ .optional(),
581
+ })
582
+ .strict()
583
+
584
+ export const ChannelRuntimeCacheSampleSchema = z
585
+ .object({
586
+ turn: z.number().int().nonnegative(),
587
+ step: z.number().int().nonnegative(),
588
+ at: z.number().nonnegative().optional(),
589
+ uncachedInputTokens: z.number().int().nonnegative(),
590
+ cacheReadTokens: z.number().int().nonnegative().optional(),
591
+ cacheWriteTokens: z.number().int().nonnegative().optional(),
592
+ })
593
+ .strict()
594
+
595
+ export const ChannelRuntimeCacheSchema = z
596
+ .object({
597
+ scope: z.literal('episode'),
598
+ aggregate: z
599
+ .object({
600
+ usageRequestCount: z.number().int().nonnegative(),
601
+ observedRequestCount: z.number().int().nonnegative(),
602
+ shareRequestCount: z.number().int().nonnegative(),
603
+ hitRequestCount: z.number().int().nonnegative(),
604
+ uncachedInputTokens: z.number().int().nonnegative(),
605
+ cacheReadTokens: z.number().int().nonnegative(),
606
+ cacheWriteTokens: z.number().int().nonnegative(),
607
+ averageRequestReadShare: z.number().min(0).max(1).optional(),
608
+ })
609
+ .strict(),
610
+ recent: z
611
+ .object({
612
+ windowSize: z.number().int().positive(),
613
+ samples: z.array(ChannelRuntimeCacheSampleSchema),
614
+ })
615
+ .strict(),
616
+ })
617
+ .strict()
618
+
619
+ export const ChannelRuntimePerformanceSampleSchema = z
620
+ .object({
621
+ turn: z.number().int().nonnegative(),
622
+ step: z.number().int().nonnegative(),
623
+ at: z.number().nonnegative().optional(),
624
+ firstTokenMs: z.number().int().nonnegative().optional(),
625
+ decodeMs: z.number().int().nonnegative().optional(),
626
+ outputTokens: z.number().int().nonnegative().optional(),
627
+ })
628
+ .strict()
629
+
630
+ export const ChannelRuntimePerformanceSchema = z
631
+ .object({
632
+ scope: z.literal('episode'),
633
+ aggregate: z
634
+ .object({
635
+ steps: z.number().int().nonnegative(),
636
+ llmMs: z.number().nonnegative(),
637
+ toolMs: z.number().nonnegative(),
638
+ ttftMs: z.number().nonnegative(),
639
+ ttftSteps: z.number().int().nonnegative(),
640
+ decodeMs: z.number().nonnegative(),
641
+ decodeTokens: z.number().int().nonnegative(),
642
+ decodeSteps: z.number().int().nonnegative(),
643
+ retryCount: z.number().int().nonnegative(),
644
+ retryDelayMs: z.number().nonnegative(),
645
+ })
646
+ .strict(),
647
+ recent: z
648
+ .object({
649
+ windowSize: z.number().int().positive(),
650
+ samples: z.array(ChannelRuntimePerformanceSampleSchema),
651
+ })
652
+ .strict(),
653
+ })
654
+ .strict()
655
+
656
+ export const ChannelRuntimeProjectionSchema = z
657
+ .object({
658
+ channelId: ChannelIdSchema,
659
+ agentId: AgentIdSchema.optional(),
660
+ episodeId: EpisodeIdSchema.optional(),
661
+ phase: ChannelRuntimePhaseSchema,
662
+ summary: z.string(),
663
+ pendingInjectCount: z.number().int().nonnegative(),
664
+ occupancy: ChannelRuntimeOccupancySchema.optional(),
665
+ cache: ChannelRuntimeCacheSchema.optional(),
666
+ performance: ChannelRuntimePerformanceSchema.optional(),
667
+ turns: z.array(ChannelRuntimeTurnSchema),
668
+ })
669
+ .strict()
670
+
671
+ export const ChannelRuntimeSseDataSchema = ChannelRuntimeProjectionSchema.extend({
672
+ revision: z.number().int().positive(),
673
+ truncated: z.boolean().optional(),
674
+ }).strict()
675
+
676
+ export type ChannelRuntimePhase = z.output<typeof ChannelRuntimePhaseSchema>
677
+ export type ChannelRuntimeUsage = z.output<typeof ChannelRuntimeUsageSchema>
678
+ export type ChannelRuntimeOccupancy = z.output<typeof ChannelRuntimeOccupancySchema>
679
+ export type ChannelRuntimeCacheSample = z.output<typeof ChannelRuntimeCacheSampleSchema>
680
+ export type ChannelRuntimeCache = z.output<typeof ChannelRuntimeCacheSchema>
681
+ export type ChannelRuntimePerformanceSample = z.output<typeof ChannelRuntimePerformanceSampleSchema>
682
+ export type ChannelRuntimePerformance = z.output<typeof ChannelRuntimePerformanceSchema>
683
+ export type ChannelRuntimeProjection = z.output<typeof ChannelRuntimeProjectionSchema>
684
+ export type ChannelRuntimeSseData = z.output<typeof ChannelRuntimeSseDataSchema>
685
+ export type ChannelFactSseData = z.output<typeof ChannelFactSseDataSchema>
686
+
687
+ const AdapterConnectionCreationSchema = z
688
+ .object({
689
+ mode: z.enum(['schema-form', 'qr-login']),
690
+ actionLabel: z.string().trim().min(1).optional(),
691
+ pendingLabel: z.string().trim().min(1).optional(),
692
+ })
693
+ .strict()
694
+
695
+ const AdapterConnectionDescriptorSchema = z
696
+ .object({
697
+ key: NonEmptyStringSchema,
698
+ displayName: z.string(),
699
+ description: z.string(),
700
+ provisioning: z.enum(['user-created', 'system-singleton']),
701
+ aliasEditable: z.boolean(),
702
+ channelDiscovery: z.enum(['host-created', 'adapter-observed']),
703
+ channelKinds: z.array(z.enum(['internal', 'direct', 'group'])).min(1),
704
+ activities: z.array(
705
+ z
706
+ .object({
707
+ key: AdapterActivityKeySchema,
708
+ scope: z.enum(['channel', 'connection']),
709
+ displayName: z.string().trim().min(1),
710
+ description: z.string().trim().min(1),
711
+ icon: HostIconNameSchema.optional(),
712
+ triggerable: z.boolean(),
713
+ channelKinds: z.array(z.enum(['internal', 'direct', 'group'])).optional(),
714
+ })
715
+ .strict(),
716
+ ),
717
+ features: z
718
+ .object({
719
+ processingFeedback: z
720
+ .object({ channelKinds: z.array(z.enum(['direct', 'group'])).min(1) })
721
+ .strict()
722
+ .optional(),
723
+ })
724
+ .strict(),
725
+ diagnostics: z.object({ receive: z.boolean(), send: z.boolean() }).strict(),
726
+ creation: AdapterConnectionCreationSchema.optional(),
727
+ /** Typed platform actions the Adapter offers to agent extensions (`ctx.nxt.platform`). */
728
+ platformActions: z
729
+ .array(
730
+ z
731
+ .object({
732
+ name: z.string().regex(/^[a-z0-9_]{1,64}$/u),
733
+ title: z.string(),
734
+ description: z.string(),
735
+ risk: z.enum(['low', 'admin']),
736
+ channelKinds: z.array(z.enum(['direct', 'group'])).min(1),
737
+ parameters: z
738
+ .object({
739
+ type: z.literal('object'),
740
+ properties: z.record(z.string(), JsonValueSchema),
741
+ required: z.array(z.string()).optional(),
742
+ })
743
+ .strict(),
744
+ })
745
+ .strict(),
746
+ )
747
+ .optional(),
748
+ rawApi: z.object({ description: z.string() }).strict().optional(),
749
+ configSchema: ConfigSchemaDocumentSchema,
750
+ })
751
+ .strict()
752
+
753
+ const AdapterCapabilityStateSchema = z
754
+ .object({
755
+ state: z.enum(['available', 'disabled', 'unsupported', 'degraded', 'unknown']),
756
+ reason: z.string().optional(),
757
+ })
758
+ .strict()
759
+
760
+ const ActivityTriggerOverridesSchema = z.record(AdapterActivityKeySchema, z.boolean())
761
+
762
+ export const HostSyncCursorSchema = z
763
+ .object({
764
+ epoch: z.string().regex(/^[a-zA-Z0-9_-]{1,100}$/u),
765
+ sequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
766
+ })
767
+ .strict()
768
+
769
+ export const AgentAppearanceSchema = z
770
+ .object({
771
+ hue: z.number().int().min(0).max(359).optional(),
772
+ avatarAssetId: AssetIdSchema.optional(),
773
+ })
774
+ .strict()
775
+
776
+ export type AgentAppearance = z.output<typeof AgentAppearanceSchema>
777
+
778
+ /** Upper bound of a reported unread count; `unreadCapped` marks that more exist. */
779
+ export const CHANNEL_UNREAD_COUNT_CAP = 99
780
+
781
+ export const ChannelActivitySummarySchema = z
782
+ .object({
783
+ lastActivityAt: z.number().int().safe().nonnegative().optional(),
784
+ lastMessage: z
785
+ .object({
786
+ role: z.enum(['member', 'agent', 'admin', 'system']),
787
+ author: z.string().max(120),
788
+ preview: z.string().max(160),
789
+ occurredAt: z.number().int().safe().nonnegative(),
790
+ })
791
+ .strict()
792
+ .optional(),
793
+ unreadCount: z.number().int().nonnegative().max(CHANNEL_UNREAD_COUNT_CAP),
794
+ unreadCapped: z.boolean(),
795
+ })
796
+ .strict()
797
+
798
+ export type ChannelActivitySummary = z.output<typeof ChannelActivitySummarySchema>
799
+
800
+ export const AttentionItemSchema = z
801
+ .object({
802
+ /** Stable fingerprint; a recurrence of the same condition gets a new fingerprint. */
803
+ id: z.string().min(1).max(200),
804
+ kind: z.enum([
805
+ 'delivery-unconfirmed',
806
+ 'delivery-failed',
807
+ 'turn-failed',
808
+ 'authoring-approval',
809
+ 'connection-unhealthy',
810
+ 'agent-model-unavailable',
811
+ 'agent-vision-unavailable',
812
+ ]),
813
+ severity: z.enum(['critical', 'warning', 'info']),
814
+ subject: z
815
+ .object({
816
+ kind: z.enum(['outbound', 'channel', 'authoring-task', 'connection', 'agent']),
817
+ id: NonEmptyStringSchema,
818
+ })
819
+ .strict(),
820
+ related: z
821
+ .object({
822
+ channelId: ChannelIdSchema.optional(),
823
+ agentId: AgentIdSchema.optional(),
824
+ connectionId: ConnectionIdSchema.optional(),
825
+ taskId: AuthoringTaskIdSchema.optional(),
826
+ outboundId: OutboundIntentIdSchema.optional(),
827
+ })
828
+ .strict(),
829
+ title: z.string().min(1).max(120),
830
+ detail: z.string().max(400),
831
+ action: z
832
+ .object({
833
+ kind: z.enum(['open-channel', 'resolve-delivery', 'open-authoring-task', 'open-connection', 'open-agent']),
834
+ label: z.string().min(1).max(40),
835
+ })
836
+ .strict(),
837
+ occurredAt: z.number().int().safe().nonnegative(),
838
+ })
839
+ .strict()
840
+
841
+ export type AttentionItem = z.output<typeof AttentionItemSchema>
842
+
843
+ export const AttentionListSchema = z
844
+ .object({
845
+ revision: z.string().min(1),
846
+ items: z.array(AttentionItemSchema).max(200),
847
+ })
848
+ .strict()
849
+
850
+ export const ChannelPendingContextSchema = z
851
+ .object({
852
+ channelId: ChannelIdSchema,
853
+ episodeId: EpisodeIdSchema.optional(),
854
+ items: z.array(
855
+ z
856
+ .object({
857
+ admissionId: NonEmptyStringSchema,
858
+ /** `next-step`: injected at the next safe step boundary; `next-turn`: waits for a later turn. */
859
+ target: z.enum(['next-step', 'next-turn']),
860
+ events: z.array(
861
+ z
862
+ .object({
863
+ eventId: ChannelEventIdSchema,
864
+ author: z.string().max(120),
865
+ preview: z.string().max(160),
866
+ receivedAt: z.number().int().safe().nonnegative(),
867
+ })
868
+ .strict(),
869
+ ),
870
+ })
871
+ .strict(),
872
+ ),
873
+ })
874
+ .strict()
875
+
876
+ export type ChannelPendingContext = z.output<typeof ChannelPendingContextSchema>
877
+
878
+ export const ACTIVITY_MAX_WINDOW_MS = 24 * 60 * 60 * 1000
879
+ export const ACTIVITY_MIN_BUCKET_MS = 60 * 1000
880
+ export const ACTIVITY_MAX_BUCKETS = 288
881
+
882
+ const ActivityDurationSchema = z.string().regex(/^[1-9][0-9]{0,4}[mh]$/u)
883
+
884
+ export const activityDurationMs = (value: string): number => {
885
+ const amount = Number(value.slice(0, -1))
886
+ return value.endsWith('h') ? amount * 60 * 60 * 1000 : amount * 60 * 1000
887
+ }
888
+
889
+ export const ActivityQuerySchema = z
890
+ .object({
891
+ window: ActivityDurationSchema.default('2h'),
892
+ bucket: ActivityDurationSchema.default('5m'),
893
+ })
894
+ .strict()
895
+ .superRefine((value, context) => {
896
+ const window = activityDurationMs(value.window)
897
+ const bucket = activityDurationMs(value.bucket)
898
+ if (window > ACTIVITY_MAX_WINDOW_MS)
899
+ context.addIssue({ code: 'custom', path: ['window'], message: '统计窗口不能超过 24 小时。' })
900
+ if (bucket < ACTIVITY_MIN_BUCKET_MS)
901
+ context.addIssue({ code: 'custom', path: ['bucket'], message: '统计间隔不能小于 1 分钟。' })
902
+ if (bucket > window) context.addIssue({ code: 'custom', path: ['bucket'], message: '统计间隔不能大于统计窗口。' })
903
+ if (window / bucket > ACTIVITY_MAX_BUCKETS) {
904
+ context.addIssue({ code: 'custom', path: ['bucket'], message: `统计桶不能超过 ${ACTIVITY_MAX_BUCKETS} 个。` })
905
+ }
906
+ })
907
+
908
+ export const ChannelActivitySeriesSchema = z
909
+ .object({
910
+ from: z.number().int().safe().nonnegative(),
911
+ to: z.number().int().safe().nonnegative(),
912
+ bucketMs: z.number().int().positive(),
913
+ channels: z.array(
914
+ z
915
+ .object({
916
+ channelId: ChannelIdSchema,
917
+ counts: z.array(z.number().int().nonnegative()).max(ACTIVITY_MAX_BUCKETS),
918
+ total: z.number().int().nonnegative(),
919
+ })
920
+ .strict(),
921
+ ),
922
+ })
923
+ .strict()
924
+
925
+ export type ChannelActivitySeries = z.output<typeof ChannelActivitySeriesSchema>
926
+
927
+ export const ScheduledTaskIdSchema = z.string().regex(/^job_[0-9A-Za-z]+$/u)
928
+
929
+ /**
930
+ * One scheduled task of an agent in one channel. `chat` tasks come from the agent's scheduling tools; `declared` and
931
+ * `runtime` tasks belong to an extension (Manifest plan or `ctx.nxt.jobs`).
932
+ */
933
+ export const ScheduledTaskSchema = z
934
+ .object({
935
+ id: ScheduledTaskIdSchema,
936
+ agentId: AgentIdSchema,
937
+ channelId: ChannelIdSchema,
938
+ source: z.enum(['chat', 'declared', 'runtime']),
939
+ extensionId: ExtensionIdSchema.optional(),
940
+ extensionName: z.string().optional(),
941
+ label: z.string(),
942
+ note: z.string().optional(),
943
+ schedule: z.discriminatedUnion('kind', [
944
+ z.object({ kind: z.literal('once'), at: z.number().int().safe() }).strict(),
945
+ z.object({ kind: z.literal('cron'), cron: z.string(), timezone: z.string() }).strict(),
946
+ ]),
947
+ /** `inactive`: the owning extension is disabled for this agent, so the task stays silent. */
948
+ state: z.enum(['scheduled', 'paused', 'finished', 'inactive']),
949
+ nextRunAt: z.number().int().safe().optional(),
950
+ lastFiredAt: z.number().int().safe().optional(),
951
+ createdAt: z.number().int().safe(),
952
+ })
953
+ .strict()
954
+
955
+ export type ScheduledTask = z.output<typeof ScheduledTaskSchema>
956
+
957
+ export const HostSnapshotSchema = z
958
+ .object({
959
+ upgrade: HostUpgradeSummarySchema.optional(),
960
+ cursor: HostSyncCursorSchema,
961
+ diagnosticsSampledAt: z.number().int().nonnegative(),
962
+ productMetadata: z
963
+ .object({
964
+ displayName: NonEmptyStringSchema,
965
+ organizationName: NonEmptyStringSchema,
966
+ version: NonEmptyStringSchema,
967
+ releaseId: NonEmptyStringSchema,
968
+ repositoryUrl: z.url(),
969
+ licenseSpdx: NonEmptyStringSchema.nullable(),
970
+ dshVersion: NonEmptyStringSchema.optional(),
971
+ })
972
+ .strict()
973
+ .optional(),
974
+ models: z.array(
975
+ z
976
+ .object({
977
+ provider: NonEmptyStringSchema,
978
+ providerName: NonEmptyStringSchema,
979
+ id: NonEmptyStringSchema,
980
+ name: z.string(),
981
+ description: z.string().optional(),
982
+ inputModalities: z.array(z.string()).optional(),
983
+ })
984
+ .strict(),
985
+ ),
986
+ capabilityAvailability: z
987
+ .object({
988
+ subagents: z.object({ available: z.boolean() }).strict(),
989
+ webSearch: z
990
+ .object({
991
+ provider: z.literal('deepseek-official'),
992
+ available: z.boolean(),
993
+ credentialConfigured: z.boolean(),
994
+ credentialReference: NonEmptyStringSchema,
995
+ maxUsesPerCall: z.number().int().nonnegative(),
996
+ maxResultsPerCall: z.number().int().nonnegative(),
997
+ timeoutMs: z.number().int().nonnegative(),
998
+ })
999
+ .strict(),
1000
+ })
1001
+ .strict(),
1002
+ connectionAdapters: z.array(AdapterConnectionDescriptorSchema),
1003
+ notificationSettings: NotificationSettingsViewSchema,
1004
+ agents: z.array(
1005
+ z
1006
+ .object({
1007
+ id: AgentIdSchema,
1008
+ displayName: z.string(),
1009
+ persona: z.string(),
1010
+ personaDocument: PromptDocumentV1Schema,
1011
+ model: AgentModelSchema,
1012
+ capabilities: AgentCapabilitiesSchema,
1013
+ imagePolicy: ImageUnderstandingPolicyApiSchema,
1014
+ dynamicClientApprovalPolicy: DynamicClientApprovalPolicySchema,
1015
+ imageDiagnostics: z
1016
+ .object({
1017
+ route: z
1018
+ .object({
1019
+ mode: z.enum(['direct', 'delegated', 'unavailable']),
1020
+ provider: z.string().optional(),
1021
+ model: z.string().optional(),
1022
+ })
1023
+ .strict(),
1024
+ activeSessions: z.number().int().nonnegative(),
1025
+ residentImages: z.number().int().nonnegative(),
1026
+ duplicateImagesSkipped: z.number().int().nonnegative(),
1027
+ lastInspection: z
1028
+ .object({
1029
+ mode: z.enum(['direct', 'delegated']),
1030
+ imageCount: z.number().int().nonnegative(),
1031
+ provider: z.string().optional(),
1032
+ model: z.string().optional(),
1033
+ cacheHit: z.boolean(),
1034
+ usage: ChannelRuntimeUsageSchema.optional(),
1035
+ errorCode: z.string().optional(),
1036
+ })
1037
+ .strict()
1038
+ .optional(),
1039
+ lastRestoration: z
1040
+ .object({
1041
+ compactionId: z.string(),
1042
+ candidateCount: z.number().int().nonnegative(),
1043
+ restoredCount: z.number().int().nonnegative(),
1044
+ skippedCount: z.number().int().nonnegative(),
1045
+ error: z.string().optional(),
1046
+ })
1047
+ .strict()
1048
+ .optional(),
1049
+ blockers: z.array(z.string()),
1050
+ })
1051
+ .strict(),
1052
+ currentRevisionId: AgentRevisionIdSchema,
1053
+ runtimeStatus: z.enum(['idle', 'running']),
1054
+ runtimePhase: ChannelRuntimePhaseSchema.default('idle'),
1055
+ createdAt: z.number().finite(),
1056
+ channels: z.array(ChannelIdSchema),
1057
+ appearance: AgentAppearanceSchema.default({}),
1058
+ })
1059
+ .strict(),
1060
+ ),
1061
+ channels: z.array(
1062
+ z
1063
+ .object({
1064
+ id: ChannelIdSchema,
1065
+ connectionId: ConnectionIdSchema,
1066
+ platformChannelId: NonEmptyStringSchema,
1067
+ kind: z.enum(['internal', 'group', 'direct']),
1068
+ displayName: z.string().optional(),
1069
+ boundAgentId: AgentIdSchema.optional(),
1070
+ runtimePhase: ChannelRuntimePhaseSchema.default('idle'),
1071
+ activity: ChannelActivitySummarySchema.default({ unreadCount: 0, unreadCapped: false }),
1072
+ bindings: z.array(
1073
+ z
1074
+ .object({
1075
+ channelId: ChannelIdSchema,
1076
+ agentId: AgentIdSchema,
1077
+ triggerPolicy: TriggerPolicySchema,
1078
+ processingFeedback: z.enum(['auto', 'off']).default('auto'),
1079
+ activityTriggerOverrides: ActivityTriggerOverridesSchema.default({}),
1080
+ boundAt: z.number().int().safe().nonnegative(),
1081
+ })
1082
+ .strict(),
1083
+ ),
1084
+ })
1085
+ .strict(),
1086
+ ),
1087
+ workTreeOrder: WorkTreeOrderSchema.default({
1088
+ agentIds: [],
1089
+ channelIdsByAgent: {},
1090
+ unboundChannelIds: [],
1091
+ }),
1092
+ messages: z.array(HostSnapshotMessageSchema),
1093
+ connections: z.array(
1094
+ z
1095
+ .object({
1096
+ id: ConnectionIdSchema,
1097
+ adapterKey: NonEmptyStringSchema,
1098
+ alias: ConnectionAliasOutputSchema.optional(),
1099
+ activityTriggerDefaults: z.array(AdapterActivityKeySchema).default([]),
1100
+ status: z
1101
+ .object({
1102
+ state: z.enum(['stopped', 'connecting', 'connected', 'reconnecting', 'failed']),
1103
+ message: z.string().optional(),
1104
+ credentialConfigured: z.boolean(),
1105
+ proactiveSend: z.boolean(),
1106
+ accountReference: z.string().optional(),
1107
+ implementation: z
1108
+ .object({
1109
+ name: z.string().optional(),
1110
+ version: z.string().optional(),
1111
+ protocolVersion: z.string().optional(),
1112
+ })
1113
+ .strict()
1114
+ .optional(),
1115
+ optionalCapabilities: z
1116
+ .record(z.string(), z.enum(['unknown', 'available', 'unsupported', 'degraded']))
1117
+ .optional(),
1118
+ activities: z.record(z.string(), AdapterCapabilityStateSchema).default({}),
1119
+ processingFeedback: AdapterCapabilityStateSchema.optional(),
1120
+ })
1121
+ .strict()
1122
+ .default({ state: 'stopped', credentialConfigured: false, proactiveSend: false, activities: {} }),
1123
+ channelCount: z.number().int().nonnegative(),
1124
+ knownChannels: z.array(
1125
+ z.object({ id: ChannelIdSchema, name: z.string(), kind: z.enum(['internal', 'group', 'direct']) }).strict(),
1126
+ ),
1127
+ lastInbound: z
1128
+ .object({
1129
+ channelId: ChannelIdSchema,
1130
+ platformMessageId: NonEmptyStringSchema,
1131
+ receivedAt: z.number().int().safe().nonnegative(),
1132
+ })
1133
+ .strict()
1134
+ .optional(),
1135
+ receiveTest: z.lazy(() => ConnectionTestResultSchema).optional(),
1136
+ sendTest: z.lazy(() => ConnectionTestResultSchema).optional(),
1137
+ configuration: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({}),
1138
+ })
1139
+ .strict(),
1140
+ ),
1141
+ archivedConnections: z
1142
+ .array(
1143
+ z
1144
+ .object({
1145
+ id: ConnectionIdSchema,
1146
+ adapterKey: NonEmptyStringSchema,
1147
+ alias: ConnectionAliasOutputSchema.optional(),
1148
+ channelCount: z.number().int().nonnegative(),
1149
+ archivedAt: z.number().int().safe().nonnegative(),
1150
+ })
1151
+ .strict(),
1152
+ )
1153
+ .default([]),
1154
+ extensions: z.array(
1155
+ z
1156
+ .object({
1157
+ id: ExtensionIdSchema,
1158
+ scope: z.enum(['agent', 'host-adapter', 'host-ui']),
1159
+ slug: NonEmptyStringSchema,
1160
+ displayName: z.string(),
1161
+ description: z.string(),
1162
+ createdByAgentId: AgentIdSchema.optional(),
1163
+ revisions: z.array(
1164
+ z
1165
+ .object({
1166
+ id: ExtensionRevisionIdSchema,
1167
+ revisionNumber: z.number().int().positive(),
1168
+ format: z.enum(['current', 'unavailable']).optional(),
1169
+ ui: ExtensionUiContributionsSchema.default(EMPTY_EXTENSION_UI_CONTRIBUTIONS),
1170
+ configSchema: ConfigSchemaDocumentSchema.optional(),
1171
+ createdAt: z.number().int().safe().nonnegative(),
1172
+ scope: z.enum(['agent', 'host-adapter', 'host-ui']),
1173
+ contributions: z.array(z.string()),
1174
+ verification: z
1175
+ .object({
1176
+ verifiedAt: z.number().int().nonnegative(),
1177
+ dshVersion: z.string(),
1178
+ contractVersion: z.string(),
1179
+ hostBuilt: z.boolean(),
1180
+ clientBuilt: z.boolean(),
1181
+ buildKey: z.string(),
1182
+ toolInvocationCount: z.number().int().nonnegative(),
1183
+ rpcMethods: z.array(z.string()),
1184
+ renderedPanels: z.array(z.string()).default([]),
1185
+ renderedToolViews: z.array(z.string()).default([]),
1186
+ renderedMessageRenderers: z.array(z.string()).default([]),
1187
+ renderedPages: z.array(HostPageContributionSchema).max(8).optional(),
1188
+ usedUiComponents: z.array(HostUiKitComponentNameSchema).optional(),
1189
+ pageGeometry: z.array(HostUiPageGeometryEvidenceSchema).max(8).optional(),
1190
+ permissions: HostUiPermissionDeclarationSchema.optional(),
1191
+ permissionDigest: z
1192
+ .string()
1193
+ .regex(/^[a-f0-9]{64}$/u)
1194
+ .optional(),
1195
+ permissionApprovalRequired: z.boolean().optional(),
1196
+ adapter: z
1197
+ .object({
1198
+ apiVersion: z.literal(2),
1199
+ key: NonEmptyStringSchema,
1200
+ descriptorDigest: z.string().regex(/^[a-f0-9]{64}$/u),
1201
+ registered: z.boolean(),
1202
+ started: z.boolean(),
1203
+ stopped: z.boolean(),
1204
+ inboundCommitted: z.boolean(),
1205
+ outboundReceipt: z.enum(['sent', 'failed', 'unknown']),
1206
+ })
1207
+ .strict()
1208
+ .optional(),
1209
+ })
1210
+ .strict()
1211
+ .optional(),
1212
+ })
1213
+ .strict(),
1214
+ ),
1215
+ activations: z.array(
1216
+ z
1217
+ .object({
1218
+ agentId: AgentIdSchema,
1219
+ extensionRevisionId: ExtensionRevisionIdSchema,
1220
+ /** Secret fields are removed; `configuredSecrets` lists those the Host stores. */
1221
+ config: JsonValueSchema,
1222
+ configuredSecrets: z.array(z.string()).optional(),
1223
+ activatedAt: z.number().int().safe().nonnegative(),
1224
+ runtime: z
1225
+ .object({
1226
+ status: z.enum(['active', 'restore-failed', 'dispose-failed']),
1227
+ message: z.string().optional(),
1228
+ observedAt: z.number().int().safe().nonnegative(),
1229
+ })
1230
+ .optional(),
1231
+ /** Latest connection attempt of each MCP server this Activation declares. */
1232
+ mcpServers: z
1233
+ .array(
1234
+ z
1235
+ .object({
1236
+ name: z.string(),
1237
+ state: z.enum(['connecting', 'connected', 'unavailable', 'missing-credentials']),
1238
+ toolCount: z.number().int().nonnegative().optional(),
1239
+ missing: z.array(z.string()).optional(),
1240
+ message: z.string().optional(),
1241
+ observedAt: z.number().int().safe().nonnegative(),
1242
+ })
1243
+ .strict(),
1244
+ )
1245
+ .optional(),
1246
+ })
1247
+ .strict(),
1248
+ ),
1249
+ installation: z
1250
+ .object({
1251
+ extensionRevisionId: ExtensionRevisionIdSchema,
1252
+ installedAt: z.number().int().safe().nonnegative(),
1253
+ config: JsonValueSchema.default({}),
1254
+ runtime: z
1255
+ .object({
1256
+ status: z.enum(['active', 'restore-failed', 'dispose-failed']),
1257
+ message: z.string().optional(),
1258
+ observedAt: z.number().int().safe().nonnegative(),
1259
+ })
1260
+ .optional(),
1261
+ })
1262
+ .strict()
1263
+ .optional(),
1264
+ hostUiPermission: z
1265
+ .object({
1266
+ declaration: HostUiPermissionDeclarationSchema,
1267
+ permissionDigest: z.string().regex(/^[a-f0-9]{64}$/u),
1268
+ approvalRequired: z.boolean(),
1269
+ })
1270
+ .strict()
1271
+ .optional(),
1272
+ hostClientDiagnostic: z
1273
+ .object({
1274
+ revisionId: ExtensionRevisionIdSchema,
1275
+ status: z.enum(['loaded', 'failed']),
1276
+ message: z.string().optional(),
1277
+ observedAt: z.number().int().safe().nonnegative(),
1278
+ })
1279
+ .strict()
1280
+ .optional(),
1281
+ clientDiagnostics: z.array(
1282
+ z
1283
+ .object({
1284
+ agentId: AgentIdSchema,
1285
+ revisionId: ExtensionRevisionIdSchema,
1286
+ status: z.enum(['loaded', 'failed']),
1287
+ message: z.string().optional(),
1288
+ observedAt: z.number().int().nonnegative(),
1289
+ })
1290
+ .strict(),
1291
+ ),
1292
+ })
1293
+ .strict(),
1294
+ ),
1295
+ hostUi: z
1296
+ .object({
1297
+ preferencesRevision: z.number().int().nonnegative(),
1298
+ pages: z.array(HostUiPageEntrySchema),
1299
+ })
1300
+ .strict()
1301
+ .default({ preferencesRevision: 0, pages: [] }),
1302
+ dynamic: z.array(
1303
+ z
1304
+ .object({
1305
+ agentId: AgentIdSchema,
1306
+ episodeId: EpisodeIdSchema,
1307
+ pluginId: DynamicIdSchema,
1308
+ packageId: DynamicIdSchema.optional(),
1309
+ currentPackageId: DynamicIdSchema.optional(),
1310
+ nextPackageId: DynamicIdSchema.optional(),
1311
+ approvalRequestId: DynamicIdSchema.optional(),
1312
+ status: DynamicAttemptStatusSchema,
1313
+ activeRun: z.object({ pluginRunId: DynamicIdSchema, packageId: DynamicIdSchema }).strict().optional(),
1314
+ latestRun: DynamicLatestRunSchema.optional(),
1315
+ packages: z.array(
1316
+ z
1317
+ .object({
1318
+ packageId: DynamicIdSchema,
1319
+ name: z.string(),
1320
+ purpose: z.string(),
1321
+ hasHostHalf: z.boolean(),
1322
+ hasClientHalf: z.boolean(),
1323
+ })
1324
+ .strict(),
1325
+ ),
1326
+ policy: z
1327
+ .object({
1328
+ turn: z.number().int().nonnegative(),
1329
+ primaryPluginId: DynamicIdSchema.optional(),
1330
+ consecutiveFailures: z.number().int().nonnegative(),
1331
+ repeatedFingerprintCount: z.number().int().nonnegative(),
1332
+ lastErrorFingerprint: z.string().optional(),
1333
+ blockedReason: z.string().optional(),
1334
+ })
1335
+ .strict(),
1336
+ })
1337
+ .strict(),
1338
+ ),
1339
+ authoringTasks: z.array(AuthoringTaskSummarySchema).default([]),
1340
+ scheduledTasks: z.array(ScheduledTaskSchema).default([]),
1341
+ })
1342
+ .strict()
1343
+
1344
+ const MessageInputPartSchema = z.discriminatedUnion('type', [
1345
+ z.object({ type: z.literal('text'), text: z.string().trim().min(1) }).strict(),
1346
+ z.object({ type: z.literal('mention'), memberId: ChannelMemberIdSchema }).strict(),
1347
+ ])
1348
+
1349
+ const SendChannelMessageRequestSchema = z
1350
+ .object({
1351
+ parts: z.array(MessageInputPartSchema).min(1),
1352
+ clientEventId: NonEmptyStringSchema.optional(),
1353
+ senderMemberId: ChannelMemberIdSchema.optional(),
1354
+ })
1355
+ .strict()
1356
+ .refine(
1357
+ (body) => body.parts.every((part) => part.type !== 'text' || [...part.text].length <= 64 * 1024),
1358
+ 'Message text must not exceed 64 KiB.',
1359
+ )
1360
+
1361
+ const DynamicErrorDetailsSchema = z.object({ message: z.string(), stack: z.string().optional() }).strict()
1362
+ const DynamicRenderFailureSchema = z
1363
+ .object({
1364
+ slot: NonEmptyStringSchema,
1365
+ message: z.string(),
1366
+ stack: z.string().optional(),
1367
+ abdicated: z.boolean(),
1368
+ })
1369
+ .strict()
1370
+ const DynamicRunResolutionSchema = z.discriminatedUnion('ok', [
1371
+ z.object({ ok: z.literal(true), pluginRunId: DynamicIdSchema, waitingFor: z.array(z.string()).optional() }).strict(),
1372
+ z
1373
+ .object({
1374
+ ok: z.literal(false),
1375
+ reason: z.enum(['rejected', 'host-half-failed', 'client-half-failed']),
1376
+ pluginRunId: DynamicIdSchema.optional(),
1377
+ startedHere: z.boolean().optional(),
1378
+ message: z.string().optional(),
1379
+ stack: z.string().optional(),
1380
+ })
1381
+ .strict(),
1382
+ ])
1383
+
1384
+ const DynamicRunResponseSchema = z.discriminatedUnion('ok', [
1385
+ z
1386
+ .object({
1387
+ ok: z.literal(true),
1388
+ status: z.enum(['awaiting-approval', 'starting', 'running']),
1389
+ pluginId: DynamicIdSchema,
1390
+ packageId: DynamicIdSchema,
1391
+ pluginRunId: DynamicIdSchema,
1392
+ waitingFor: z.array(z.string()),
1393
+ clientWaitingFor: z.array(z.string()).optional(),
1394
+ currentPackageId: DynamicIdSchema.optional(),
1395
+ nextPackageId: DynamicIdSchema.optional(),
1396
+ mode: z.enum(['run', 'update']),
1397
+ })
1398
+ .strict(),
1399
+ z
1400
+ .object({
1401
+ ok: z.literal(false),
1402
+ reason: z.enum([
1403
+ 'plugin-missing',
1404
+ 'package-missing',
1405
+ 'invalid-mode',
1406
+ 'transition-in-flight',
1407
+ 'host-half-failed',
1408
+ 'client-half-failed',
1409
+ 'rejected',
1410
+ 'cancelled',
1411
+ 'not-running',
1412
+ ]),
1413
+ message: z.string(),
1414
+ stack: z.string().optional(),
1415
+ })
1416
+ .strict(),
1417
+ ])
1418
+
1419
+ const DynamicInventoryRowSchema = z
1420
+ .object({
1421
+ pluginId: DynamicIdSchema,
1422
+ agentId: NonEmptyStringSchema,
1423
+ packages: z.array(
1424
+ z
1425
+ .object({
1426
+ packageId: DynamicIdSchema,
1427
+ name: z.string(),
1428
+ purpose: z.string(),
1429
+ hasHostHalf: z.boolean(),
1430
+ hasClientHalf: z.boolean(),
1431
+ })
1432
+ .strict(),
1433
+ ),
1434
+ currentPackageId: DynamicIdSchema.optional(),
1435
+ nextPackageId: DynamicIdSchema.optional(),
1436
+ activeRun: z.object({ pluginRunId: DynamicIdSchema, packageId: DynamicIdSchema }).strict().optional(),
1437
+ latestRun: DynamicLatestRunSchema.optional(),
1438
+ })
1439
+ .strict()
1440
+
1441
+ export const DshPluginCatalogEntrySchema = z
1442
+ .object({
1443
+ packageName: NonEmptyStringSchema,
1444
+ packageVersion: NonEmptyStringSchema,
1445
+ origin: z.enum(['builtin', 'profile', 'dynamic', 'installed']),
1446
+ settingsNamespaces: z.array(NonEmptyStringSchema),
1447
+ loadError: z.object({ code: NonEmptyStringSchema, message: NonEmptyStringSchema }).strict().optional(),
1448
+ packageId: DshPluginPackageIdSchema.optional(),
1449
+ installSource: z.enum(['registry', 'tarball', 'imported']).optional(),
1450
+ installedAt: z.number().int().safe().nonnegative().optional(),
1451
+ clientUiDetected: z.boolean().optional(),
1452
+ hostUi: DshNxtHostUiSchema.optional(),
1453
+ approvedBuilds: z.array(NonEmptyStringSchema).optional(),
1454
+ entries: z
1455
+ .array(
1456
+ z
1457
+ .object({
1458
+ id: DshPluginEntryIdSchema,
1459
+ entryKey: NonEmptyStringSchema,
1460
+ moduleName: NonEmptyStringSchema,
1461
+ suggestedScope: z.enum(['host', 'agent']),
1462
+ selectedScope: z.enum(['host', 'agent']).optional(),
1463
+ config: JsonValueSchema,
1464
+ activations: z.array(
1465
+ z
1466
+ .object({
1467
+ targetKey: NonEmptyStringSchema,
1468
+ target: z.enum(['host', 'agent']),
1469
+ agentId: AgentIdSchema.optional(),
1470
+ activatedAt: z.number().int().safe().nonnegative(),
1471
+ diagnostic: z
1472
+ .object({
1473
+ status: z.enum(['active', 'load-failed', 'restore-failed', 'dispose-failed']),
1474
+ phase: z.enum(['import', 'apply', 'update', 'dispose', 'restore']),
1475
+ message: z.string().optional(),
1476
+ observedAt: z.number().int().safe().nonnegative(),
1477
+ })
1478
+ .optional(),
1479
+ })
1480
+ .strict(),
1481
+ ),
1482
+ })
1483
+ .strict(),
1484
+ )
1485
+ .optional(),
1486
+ })
1487
+ .strict()
1488
+
1489
+ export const DshSettingsNamespaceSchema = z
1490
+ .object({
1491
+ ns: NonEmptyStringSchema,
1492
+ schema: JsonValueSchema,
1493
+ resolved: JsonValueSchema,
1494
+ base: JsonValueSchema.optional(),
1495
+ user: JsonValueSchema.optional(),
1496
+ applies: z.enum(['live', 'restart']),
1497
+ secrets: z.array(z.object({ path: z.array(z.string()), set: z.boolean() }).strict()),
1498
+ revision: z.number().int().nonnegative(),
1499
+ writable: z.boolean(),
1500
+ owner: z.object({ packageName: NonEmptyStringSchema, packageVersion: NonEmptyStringSchema }).strict().optional(),
1501
+ })
1502
+ .strict()
1503
+
1504
+ export const DshSettingsPathSchema = z.array(z.string().min(1).max(200)).min(1).max(32)
1505
+
1506
+ export const DshSettingsPathOperationSchema = z.discriminatedUnion('op', [
1507
+ z.object({ op: z.literal('set'), path: DshSettingsPathSchema, value: JsonValueSchema }).strict(),
1508
+ z.object({ op: z.literal('unset'), path: DshSettingsPathSchema }).strict(),
1509
+ ])
1510
+
1511
+ export const DshSettingsMutationRequestSchema = z
1512
+ .object({
1513
+ expectedRevision: z.number().int().nonnegative(),
1514
+ ops: z.array(DshSettingsPathOperationSchema).min(1).max(128),
1515
+ })
1516
+ .strict()
1517
+
1518
+ export const DshCredentialRefSchema = z
1519
+ .string()
1520
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/u)
1521
+ .max(200)
1522
+
1523
+ export const DshCredentialViewSchema = z
1524
+ .object({ configured: z.boolean(), source: z.string().optional(), writable: z.boolean() })
1525
+ .strict()
1526
+
1527
+ export const DshSettingsChangedSseDataSchema = z
1528
+ .object({ namespace: NonEmptyStringSchema, revision: z.number().int().nonnegative() })
1529
+ .strict()
1530
+
1531
+ export const DshCredentialsChangedSseDataSchema = z.object({ ref: DshCredentialRefSchema }).strict()
1532
+
1533
+ export const DynamicChangedSseDataSchema = z.object({ agentId: AgentIdSchema }).strict()
1534
+
1535
+ export const HostSseStatusDataSchema = z
1536
+ .object({
1537
+ ok: z.boolean(),
1538
+ message: z.string(),
1539
+ replay: z.enum(['none', 'complete', 'expired']).optional(),
1540
+ })
1541
+ .strict()
1542
+
1543
+ export const DshPluginOperationSseDataSchema = z
1544
+ .object({
1545
+ operationId: z.string().uuid(),
1546
+ kind: z.enum(['inspect', 'install']),
1547
+ phase: z.enum(['download', 'dependencies', 'build-scripts', 'validation', 'publish']),
1548
+ status: z.enum(['running', 'done', 'failed']),
1549
+ message: z.string(),
1550
+ })
1551
+ .strict()
1552
+
1553
+ export const HostSseEventSchema = z.discriminatedUnion('event', [
1554
+ z.object({ event: z.literal('snapshot-changed'), data: z.object({ changed: z.literal(true) }).strict() }).strict(),
1555
+ z.object({ event: z.literal('channel-fact'), data: ChannelFactSseDataSchema }).strict(),
1556
+ z.object({ event: z.literal('connection-fact'), data: HostConnectionEventSchema }).strict(),
1557
+ z.object({ event: z.literal('runtime'), data: ChannelRuntimeSseDataSchema }).strict(),
1558
+ z.object({ event: z.literal('extensions-changed'), data: z.object({ changed: z.literal(true) }).strict() }).strict(),
1559
+ z.object({ event: z.literal('dsh-plugins-changed'), data: z.object({ changed: z.literal(true) }).strict() }).strict(),
1560
+ z.object({ event: z.literal('dsh-plugin-operation'), data: DshPluginOperationSseDataSchema }).strict(),
1561
+ z.object({ event: z.literal('dynamic-changed'), data: DynamicChangedSseDataSchema }).strict(),
1562
+ z.object({ event: z.literal('dsh-settings-changed'), data: DshSettingsChangedSseDataSchema }).strict(),
1563
+ z.object({ event: z.literal('dsh-credentials-changed'), data: DshCredentialsChangedSseDataSchema }).strict(),
1564
+ z.object({ event: z.literal('status'), data: HostSseStatusDataSchema }).strict(),
1565
+ z
1566
+ .object({ event: z.literal('attention-changed'), data: z.object({ revision: z.string().min(1) }).strict() })
1567
+ .strict(),
1568
+ z
1569
+ .object({
1570
+ event: z.literal('binding-change'),
1571
+ data: z
1572
+ .object({
1573
+ operationId: NonEmptyStringSchema,
1574
+ channelId: ChannelIdSchema,
1575
+ kind: z.enum(['bind', 'replace', 'clear']),
1576
+ step: NonEmptyStringSchema,
1577
+ status: z.enum(['running', 'skipped', 'done', 'failed']),
1578
+ message: z.string(),
1579
+ })
1580
+ .strict(),
1581
+ })
1582
+ .strict(),
1583
+ ])
1584
+
1585
+ export type HostSseEvent = z.output<typeof HostSseEventSchema>
1586
+
1587
+ /** Request content a model accepts; `image` enables native vision for agents using the model. */
1588
+ export const LlmModelInputModalitiesSchema = z.array(z.enum(['text', 'image'])).min(1)
1589
+
1590
+ export const LlmProviderModelSchema = z
1591
+ .object({
1592
+ id: NonEmptyStringSchema,
1593
+ name: NonEmptyStringSchema,
1594
+ contextWindow: z.number().int().positive().optional(),
1595
+ maxTokens: z.number().int().positive().optional(),
1596
+ inputModalities: LlmModelInputModalitiesSchema.optional(),
1597
+ })
1598
+ .strict()
1599
+
1600
+ export const LlmDiscoveredModelSchema = z
1601
+ .object({
1602
+ id: NonEmptyStringSchema,
1603
+ name: NonEmptyStringSchema.optional(),
1604
+ contextWindow: z.number().int().positive().optional(),
1605
+ maxTokens: z.number().int().positive().optional(),
1606
+ inputModalities: LlmModelInputModalitiesSchema.optional(),
1607
+ })
1608
+ .strict()
1609
+
1610
+ export const LlmProviderCredentialSchema = z
1611
+ .object({ configured: z.boolean(), source: z.string().optional(), writable: z.boolean() })
1612
+ .strict()
1613
+
1614
+ export const LlmProviderViewSchema = z
1615
+ .object({
1616
+ provider: NonEmptyStringSchema,
1617
+ displayName: z.string(),
1618
+ settingsNs: NonEmptyStringSchema,
1619
+ settingsPath: z.array(NonEmptyStringSchema),
1620
+ settingsRevision: z.number().int().nonnegative(),
1621
+ declared: z.boolean(),
1622
+ active: z.boolean(),
1623
+ configured: z.boolean(),
1624
+ baseURL: z.string().optional(),
1625
+ api: NonEmptyStringSchema.optional(),
1626
+ credential: LlmProviderCredentialSchema.optional(),
1627
+ models: z.array(LlmProviderModelSchema),
1628
+ /** The model list was edited on this host; built-in providers can restore their own catalog. */
1629
+ modelsCustomized: z.boolean(),
1630
+ /** The provider's adapter can list the models its endpoint offers. */
1631
+ discoverable: z.boolean(),
1632
+ })
1633
+ .strict()
1634
+
1635
+ export const LlmProviderSettingsSchema = z
1636
+ .object({
1637
+ writable: z.boolean(),
1638
+ protocols: z.array(NonEmptyStringSchema),
1639
+ providers: z.array(LlmProviderViewSchema),
1640
+ })
1641
+ .strict()
1642
+
1643
+ export const ConnectionTestResultSchema = z.discriminatedUnion('status', [
1644
+ z
1645
+ .object({
1646
+ status: z.literal('received'),
1647
+ channelId: ChannelIdSchema,
1648
+ platformMessageId: NonEmptyStringSchema,
1649
+ })
1650
+ .strict(),
1651
+ z
1652
+ .object({
1653
+ status: z.literal('sent'),
1654
+ channelId: ChannelIdSchema,
1655
+ platformMessageId: NonEmptyStringSchema.optional(),
1656
+ })
1657
+ .strict(),
1658
+ z
1659
+ .object({
1660
+ status: z.enum(['waiting-for-message', 'needs-channel', 'needs-target', 'not-connected']),
1661
+ message: z.string(),
1662
+ })
1663
+ .strict(),
1664
+ z
1665
+ .object({
1666
+ status: z.literal('failed'),
1667
+ kind: NonEmptyStringSchema,
1668
+ message: z.string(),
1669
+ retryAfterMs: z.number().int().nonnegative().optional(),
1670
+ })
1671
+ .strict(),
1672
+ ])
1673
+
1674
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
1675
+ type AnySchema = z.ZodType
1676
+
1677
+ export interface HostApiContract<
1678
+ Method extends HttpMethod = HttpMethod,
1679
+ Params extends AnySchema = AnySchema,
1680
+ Request extends AnySchema = AnySchema,
1681
+ Response extends AnySchema = AnySchema,
1682
+ ErrorSchema extends AnySchema = AnySchema,
1683
+ > {
1684
+ readonly method: Method
1685
+ readonly timeoutMs?: number
1686
+ readonly responseFormat?: 'json' | 'bytes'
1687
+ readonly invalidatesSnapshot?: boolean
1688
+ encodeRequest?(input: z.output<Request>): {
1689
+ readonly body: string | Uint8Array
1690
+ readonly headers: Readonly<Record<string, string>>
1691
+ }
1692
+ readonly path: string
1693
+ readonly params: Params
1694
+ readonly request: Request
1695
+ readonly response: Response
1696
+ readonly error: ErrorSchema
1697
+ readonly parseParams: (input: unknown) => z.output<Params>
1698
+ readonly parseRequest: (input: unknown) => z.output<Request>
1699
+ readonly parseResponse: (input: unknown) => z.output<Response>
1700
+ readonly parseError: (input: unknown) => z.output<ErrorSchema>
1701
+ }
1702
+
1703
+ /** 扩展包导入检查的结果:本地文件与社区下载共用同一确认步骤。 */
1704
+ export const ExtensionImportInspectionSchema = z
1705
+ .object({
1706
+ token: NonEmptyStringSchema,
1707
+ extensionId: ExtensionIdSchema,
1708
+ revisionId: ExtensionRevisionIdSchema,
1709
+ slug: NonEmptyStringSchema,
1710
+ displayName: NonEmptyStringSchema,
1711
+ scope: z.enum(['agent', 'host-adapter', 'host-ui']),
1712
+ idempotent: z.boolean(),
1713
+ slugConflict: z.boolean(),
1714
+ })
1715
+ .strict()
1716
+
1717
+ const defineContract = <
1718
+ const Method extends HttpMethod,
1719
+ Params extends AnySchema,
1720
+ Request extends AnySchema,
1721
+ Response extends AnySchema,
1722
+ >(
1723
+ contract: Omit<
1724
+ HostApiContract<Method, Params, Request, Response, typeof HostApiErrorSchema>,
1725
+ 'parseParams' | 'parseRequest' | 'parseResponse' | 'parseError'
1726
+ >,
1727
+ ): HostApiContract<Method, Params, Request, Response, typeof HostApiErrorSchema> => ({
1728
+ ...contract,
1729
+ parseParams: (input) => contract.params.parse(input),
1730
+ parseRequest: (input) => contract.request.parse(input),
1731
+ parseResponse: (input) => contract.response.parse(input),
1732
+ parseError: (input) => contract.error.parse(input),
1733
+ })
1734
+
1735
+ const agentParam = z.object({ agentId: AgentIdSchema }).strict()
1736
+ const agentRevisionParam = z.object({ agentId: AgentIdSchema, revisionId: AgentRevisionIdSchema }).strict()
1737
+
1738
+ export const AgentRevisionChangedFieldSchema = z.enum([
1739
+ 'name',
1740
+ 'persona',
1741
+ 'model',
1742
+ 'capabilities',
1743
+ 'imagePolicy',
1744
+ 'approvalPolicy',
1745
+ ])
1746
+
1747
+ export const AgentRevisionHistorySchema = z
1748
+ .object({
1749
+ agentId: AgentIdSchema,
1750
+ currentRevisionId: AgentRevisionIdSchema,
1751
+ /** Newest first. `changedFields` compares each Revision with the one numbered just before it. */
1752
+ revisions: z.array(
1753
+ z
1754
+ .object({
1755
+ id: AgentRevisionIdSchema,
1756
+ revision: z.number().int().positive(),
1757
+ createdAt: z.number().int().nonnegative(),
1758
+ displayName: z.string(),
1759
+ model: z.object({ provider: z.string(), model: z.string() }).strict(),
1760
+ changedFields: z.array(AgentRevisionChangedFieldSchema),
1761
+ current: z.boolean(),
1762
+ })
1763
+ .strict(),
1764
+ ),
1765
+ })
1766
+ .strict()
1767
+ export type AgentRevisionHistory = z.output<typeof AgentRevisionHistorySchema>
1768
+ const channelParam = z.object({ channelId: ChannelIdSchema }).strict()
1769
+ const connectionParam = z.object({ connectionId: ConnectionIdSchema }).strict()
1770
+ const agentExtensionParam = z.object({ agentId: AgentIdSchema, extensionId: ExtensionIdSchema }).strict()
1771
+ const extensionParam = z.object({ extensionId: ExtensionIdSchema }).strict()
1772
+ const dshPluginPackageParam = z.object({ packageId: DshPluginPackageIdSchema }).strict()
1773
+ const dshPluginEntryParam = z.object({ entryId: DshPluginEntryIdSchema }).strict()
1774
+ const extensionRevisionParam = z
1775
+ .object({ extensionId: ExtensionIdSchema, revisionId: ExtensionRevisionIdSchema })
1776
+ .strict()
1777
+ const dshSettingsParam = z.object({ namespace: NonEmptyStringSchema }).strict()
1778
+ const dshCredentialParam = z.object({ ref: DshCredentialRefSchema }).strict()
1779
+ const llmProviderParam = z.object({ provider: NonEmptyStringSchema }).strict()
1780
+ const authoringTaskParam = z.object({ taskId: AuthoringTaskIdSchema }).strict()
1781
+ const scheduledTaskParam = z.object({ taskId: ScheduledTaskIdSchema }).strict()
1782
+ const authoringAttemptParam = z.object({ taskId: AuthoringTaskIdSchema, attemptId: AuthoringAttemptIdSchema }).strict()
1783
+
1784
+ const DshPluginInstallInspectionSchema = z
1785
+ .object({
1786
+ token: NonEmptyStringSchema,
1787
+ operationId: z.string().uuid().optional(),
1788
+ packageName: NonEmptyStringSchema,
1789
+ packageVersion: NonEmptyStringSchema,
1790
+ packageDigest: z.string().regex(/^[a-f0-9]{64}$/u),
1791
+ lockfileDigest: z.string().regex(/^[a-f0-9]{64}$/u),
1792
+ blockedBuilds: z.array(NonEmptyStringSchema),
1793
+ clientUiDetected: z.boolean(),
1794
+ hostUi: DshNxtHostUiSchema.optional(),
1795
+ entries: z.array(
1796
+ z
1797
+ .object({
1798
+ entryKey: NonEmptyStringSchema,
1799
+ moduleName: NonEmptyStringSchema,
1800
+ suggestedScope: z.enum(['host', 'agent']),
1801
+ })
1802
+ .strict(),
1803
+ ),
1804
+ })
1805
+ .strict()
1806
+
1807
+ const BinaryUploadRequestSchema = z
1808
+ .object({
1809
+ bytes: z.instanceof(Uint8Array),
1810
+ fileName: z.string().max(255).optional(),
1811
+ operationId: z.string().uuid().optional(),
1812
+ })
1813
+ .strict()
1814
+ const encodeBinaryUpload = (input: z.output<typeof BinaryUploadRequestSchema>) => ({
1815
+ body: input.bytes,
1816
+ headers: {
1817
+ 'content-type': 'application/octet-stream',
1818
+ ...(input.fileName === undefined ? {} : { 'x-file-name': input.fileName }),
1819
+ ...(input.operationId === undefined ? {} : { 'x-operation-id': input.operationId }),
1820
+ },
1821
+ })
1822
+
1823
+ export const HostApiContracts = {
1824
+ retryRuntimeCompatibility: defineContract({
1825
+ method: 'POST',
1826
+ path: '/api/runtime/compatibility/retry',
1827
+ params: EmptyParamsSchema,
1828
+ request: z
1829
+ .object({ objectKind: RuntimeCompatibilityDiagnosticSchema.shape.objectKind, objectId: z.string().min(1) })
1830
+ .strict(),
1831
+ response: z.object({ diagnostics: z.array(RuntimeCompatibilityDiagnosticSchema) }).strict(),
1832
+ error: HostApiErrorSchema,
1833
+ invalidatesSnapshot: true,
1834
+ }),
1835
+ snapshot: defineContract({
1836
+ method: 'GET',
1837
+ path: '/api/snapshot',
1838
+ params: EmptyParamsSchema,
1839
+ request: NoRequestBodySchema,
1840
+ response: HostSnapshotSchema,
1841
+ error: HostApiErrorSchema,
1842
+ }),
1843
+ getAuthoringTask: defineContract({
1844
+ method: 'GET',
1845
+ path: '/api/authoring/tasks/:taskId',
1846
+ params: authoringTaskParam,
1847
+ request: NoRequestBodySchema,
1848
+ response: z
1849
+ .object({
1850
+ task: AuthoringTaskSummarySchema,
1851
+ attempts: z.array(AuthoringAttemptSummarySchema),
1852
+ /** Secret fields of the latest candidate and which have a test value for this task's dynamic runs. */
1853
+ testSecrets: z
1854
+ .object({
1855
+ fields: z.array(z.object({ key: z.string(), title: z.string() }).strict()),
1856
+ configured: z.array(z.string()),
1857
+ })
1858
+ .strict()
1859
+ .optional(),
1860
+ events: z.array(
1861
+ z
1862
+ .object({
1863
+ sequence: z.number().int().positive(),
1864
+ kind: z.string(),
1865
+ attemptId: AuthoringAttemptIdSchema.optional(),
1866
+ payload: JsonValueSchema,
1867
+ createdAt: z.number().int().nonnegative(),
1868
+ })
1869
+ .strict(),
1870
+ ),
1871
+ })
1872
+ .strict(),
1873
+ error: HostApiErrorSchema,
1874
+ }),
1875
+ decideAuthoringAttempt: defineContract({
1876
+ invalidatesSnapshot: true,
1877
+ method: 'POST',
1878
+ path: '/api/authoring/tasks/:taskId/attempts/:attemptId/decision',
1879
+ params: authoringAttemptParam,
1880
+ request: z
1881
+ .object({ expectedRevision: z.number().int().positive(), approved: z.boolean(), approveRiskStable: z.boolean() })
1882
+ .strict(),
1883
+ response: z
1884
+ .object({ accepted: z.boolean(), taskRevision: z.number().int().positive(), executionRequired: z.boolean() })
1885
+ .strict(),
1886
+ error: HostApiErrorSchema,
1887
+ }),
1888
+ restoreAuthoringAttempt: defineContract({
1889
+ invalidatesSnapshot: true,
1890
+ method: 'POST',
1891
+ path: '/api/authoring/tasks/:taskId/attempts/:attemptId/restore',
1892
+ params: authoringAttemptParam,
1893
+ request: z.object({ expectedRevision: z.number().int().positive() }).strict(),
1894
+ response: AuthoringTaskSummarySchema,
1895
+ error: HostApiErrorSchema,
1896
+ }),
1897
+ stopAuthoringTask: defineContract({
1898
+ invalidatesSnapshot: true,
1899
+ method: 'POST',
1900
+ path: '/api/authoring/tasks/:taskId/stop',
1901
+ params: authoringTaskParam,
1902
+ request: z.object({ expectedRevision: z.number().int().positive() }).strict(),
1903
+ response: AuthoringTaskSummarySchema,
1904
+ error: HostApiErrorSchema,
1905
+ }),
1906
+ setAuthoringTestSecrets: defineContract({
1907
+ method: 'PUT',
1908
+ path: '/api/authoring/tasks/:taskId/test-secrets',
1909
+ params: authoringTaskParam,
1910
+ request: z.object({ secrets: z.record(z.string(), z.string().max(8192)) }).strict(),
1911
+ response: z.object({ configured: z.array(z.string()) }).strict(),
1912
+ error: HostApiErrorSchema,
1913
+ }),
1914
+ deleteAuthoringTask: defineContract({
1915
+ invalidatesSnapshot: true,
1916
+ method: 'DELETE',
1917
+ path: '/api/authoring/tasks/:taskId',
1918
+ params: authoringTaskParam,
1919
+ request: NoRequestBodySchema,
1920
+ response: z.object({ deleted: z.literal(true) }).strict(),
1921
+ error: HostApiErrorSchema,
1922
+ }),
1923
+ pauseScheduledTask: defineContract({
1924
+ invalidatesSnapshot: true,
1925
+ method: 'POST',
1926
+ path: '/api/scheduled-tasks/:taskId/pause',
1927
+ params: scheduledTaskParam,
1928
+ request: NoRequestBodySchema,
1929
+ response: ScheduledTaskSchema,
1930
+ error: HostApiErrorSchema,
1931
+ }),
1932
+ resumeScheduledTask: defineContract({
1933
+ invalidatesSnapshot: true,
1934
+ method: 'POST',
1935
+ path: '/api/scheduled-tasks/:taskId/resume',
1936
+ params: scheduledTaskParam,
1937
+ request: NoRequestBodySchema,
1938
+ response: ScheduledTaskSchema,
1939
+ error: HostApiErrorSchema,
1940
+ }),
1941
+ runScheduledTask: defineContract({
1942
+ invalidatesSnapshot: true,
1943
+ method: 'POST',
1944
+ path: '/api/scheduled-tasks/:taskId/run',
1945
+ params: scheduledTaskParam,
1946
+ request: NoRequestBodySchema,
1947
+ /** `woke: false` when the owning extension's due-job handler let this run pass without waking the agent. */
1948
+ response: z.object({ task: ScheduledTaskSchema, woke: z.boolean() }).strict(),
1949
+ error: HostApiErrorSchema,
1950
+ }),
1951
+ deleteScheduledTask: defineContract({
1952
+ invalidatesSnapshot: true,
1953
+ method: 'DELETE',
1954
+ path: '/api/scheduled-tasks/:taskId',
1955
+ params: scheduledTaskParam,
1956
+ request: NoRequestBodySchema,
1957
+ response: z.object({ deleted: z.literal(true) }).strict(),
1958
+ error: HostApiErrorSchema,
1959
+ }),
1960
+ listClientNotifications: defineContract({
1961
+ method: 'GET',
1962
+ path: '/api/client-notifications',
1963
+ params: z.object({ cursor: z.number().int().nonnegative().optional() }).strict(),
1964
+ request: NoRequestBodySchema,
1965
+ response: ClientNotificationFeedResponseSchema,
1966
+ error: HostApiErrorSchema,
1967
+ }),
1968
+ updateNotificationSettings: defineContract({
1969
+ invalidatesSnapshot: true,
1970
+ method: 'PUT',
1971
+ path: '/api/settings/notifications',
1972
+ params: EmptyParamsSchema,
1973
+ request: UpdateNotificationSettingsRequestSchema,
1974
+ response: NotificationSettingsViewSchema,
1975
+ error: HostApiErrorSchema,
1976
+ }),
1977
+ testBarkNotification: defineContract({
1978
+ method: 'POST',
1979
+ path: '/api/settings/notifications/test',
1980
+ params: EmptyParamsSchema,
1981
+ request: TestBarkNotificationRequestSchema,
1982
+ response: z.object({ sent: z.literal(true) }).strict(),
1983
+ error: HostApiErrorSchema,
1984
+ }),
1985
+ testSystemNotification: defineContract({
1986
+ method: 'POST',
1987
+ path: '/api/settings/notifications/test-system',
1988
+ params: EmptyParamsSchema,
1989
+ request: NoRequestBodySchema,
1990
+ response: z.object({ published: z.literal(true) }).strict(),
1991
+ error: HostApiErrorSchema,
1992
+ }),
1993
+ listPlatformUsers: defineContract({
1994
+ method: 'GET',
1995
+ path: '/api/platform-users',
1996
+ params: z
1997
+ .object({
1998
+ query: z.string().trim().max(80).optional(),
1999
+ adapterKey: z.string().trim().min(1).max(120).optional(),
2000
+ connectionId: ConnectionIdSchema.optional(),
2001
+ cursor: PlatformIdentityIdSchema.optional(),
2002
+ limit: z.number().int().min(1).max(100).default(50),
2003
+ })
2004
+ .strict(),
2005
+ request: NoRequestBodySchema,
2006
+ response: PlatformUserListResponseSchema,
2007
+ error: HostApiErrorSchema,
2008
+ }),
2009
+ listConnectionEvents: defineContract({
2010
+ method: 'GET',
2011
+ path: '/api/connections/:connectionId/events',
2012
+ params: z
2013
+ .object({
2014
+ connectionId: ConnectionIdSchema,
2015
+ beforeReceivedAt: z.number().int().safe().nonnegative().optional(),
2016
+ beforeId: ConnectionEventIdSchema.optional(),
2017
+ limit: z.number().int().min(1).max(100).default(50),
2018
+ })
2019
+ .strict(),
2020
+ request: NoRequestBodySchema,
2021
+ response: z.object({ events: z.array(HostConnectionEventSchema), hasMore: z.boolean() }).strict(),
2022
+ error: HostApiErrorSchema,
2023
+ }),
2024
+ createAgent: defineContract({
2025
+ invalidatesSnapshot: true,
2026
+ method: 'POST',
2027
+ path: '/api/agents',
2028
+ params: EmptyParamsSchema,
2029
+ request: CreateAgentRequestSchema,
2030
+ response: z
2031
+ .object({ agentId: AgentIdSchema, channelId: ChannelIdSchema, connectionId: ConnectionIdSchema })
2032
+ .strict(),
2033
+ error: HostApiErrorSchema,
2034
+ }),
2035
+ reviseAgent: defineContract({
2036
+ invalidatesSnapshot: true,
2037
+ method: 'POST',
2038
+ path: '/api/agents/:agentId/revision',
2039
+ params: agentParam,
2040
+ request: ReviseAgentRequestSchema,
2041
+ response: z.object({ currentRevisionId: AgentRevisionIdSchema }).strict(),
2042
+ error: HostApiErrorSchema,
2043
+ }),
2044
+ updateAgentCapabilities: defineContract({
2045
+ invalidatesSnapshot: true,
2046
+ method: 'POST',
2047
+ path: '/api/agents/:agentId/capabilities',
2048
+ params: agentParam,
2049
+ request: UpdateAgentCapabilitiesRequestSchema,
2050
+ response: z.object({ currentRevisionId: AgentRevisionIdSchema, capabilities: AgentCapabilitiesSchema }).strict(),
2051
+ error: HostApiErrorSchema,
2052
+ }),
2053
+ deleteAgent: defineContract({
2054
+ invalidatesSnapshot: true,
2055
+ method: 'DELETE',
2056
+ path: '/api/agents/:agentId',
2057
+ params: agentParam,
2058
+ request: z
2059
+ .object({
2060
+ expectedCurrentRevisionId: AgentRevisionIdSchema,
2061
+ confirmationName: z.string().min(1).max(80),
2062
+ deleteAutoCreatedBuiltInChannels: z.boolean().default(true),
2063
+ /** Also remove `workspaces/<agentId>/` (files, development shell output, creation candidates). */
2064
+ deleteWorkspace: z.boolean().default(false),
2065
+ })
2066
+ .strict(),
2067
+ response: z
2068
+ .object({
2069
+ agentId: AgentIdSchema,
2070
+ deleted: z.literal(true),
2071
+ unboundChannelIds: z.array(ChannelIdSchema),
2072
+ deletedChannelIds: z.array(ChannelIdSchema),
2073
+ })
2074
+ .strict(),
2075
+ error: HostApiErrorSchema,
2076
+ }),
2077
+ createInternalChannel: defineContract({
2078
+ invalidatesSnapshot: true,
2079
+ method: 'POST',
2080
+ path: '/api/channels',
2081
+ params: EmptyParamsSchema,
2082
+ request: z.object({ displayName: z.string().trim().min(1).max(120) }).strict(),
2083
+ response: z.object({ channelId: ChannelIdSchema, connectionId: ConnectionIdSchema }).strict(),
2084
+ error: HostApiErrorSchema,
2085
+ }),
2086
+ deleteChannel: defineContract({
2087
+ invalidatesSnapshot: true,
2088
+ method: 'DELETE',
2089
+ path: '/api/channels/:channelId',
2090
+ params: channelParam,
2091
+ request: z.object({ expectedBoundAgentId: AgentIdSchema.nullable() }).strict(),
2092
+ response: z.object({ channelId: ChannelIdSchema, deleted: z.literal(true) }).strict(),
2093
+ error: HostApiErrorSchema,
2094
+ }),
2095
+ createBinding: defineContract({
2096
+ invalidatesSnapshot: true,
2097
+ method: 'POST',
2098
+ path: '/api/bindings',
2099
+ params: EmptyParamsSchema,
2100
+ request: z
2101
+ .object({
2102
+ agentId: AgentIdSchema,
2103
+ channelId: ChannelIdSchema,
2104
+ triggerPolicy: TriggerPolicySchema,
2105
+ processingFeedback: z.enum(['auto', 'off']).optional(),
2106
+ activityTriggerOverrides: ActivityTriggerOverridesSchema.optional(),
2107
+ })
2108
+ .strict(),
2109
+ response: z
2110
+ .object({
2111
+ channelId: ChannelIdSchema,
2112
+ agentId: AgentIdSchema,
2113
+ triggerPolicy: TriggerPolicySchema,
2114
+ processingFeedback: z.enum(['auto', 'off']).default('auto'),
2115
+ activityTriggerOverrides: ActivityTriggerOverridesSchema.default({}),
2116
+ boundAt: z.number().int().safe().nonnegative(),
2117
+ })
2118
+ .strict(),
2119
+ error: HostApiErrorSchema,
2120
+ }),
2121
+ clearBinding: defineContract({
2122
+ invalidatesSnapshot: true,
2123
+ method: 'DELETE',
2124
+ path: '/api/bindings/:channelId',
2125
+ params: channelParam,
2126
+ request: NoRequestBodySchema,
2127
+ response: z.object({ channelId: ChannelIdSchema, cleared: z.literal(true) }).strict(),
2128
+ error: HostApiErrorSchema,
2129
+ }),
2130
+ putWorkTreeOrder: defineContract({
2131
+ invalidatesSnapshot: true,
2132
+ method: 'PUT',
2133
+ path: '/api/work-tree-order',
2134
+ params: EmptyParamsSchema,
2135
+ request: WorkTreeOrderSchema,
2136
+ response: WorkTreeOrderSchema,
2137
+ error: HostApiErrorSchema,
2138
+ }),
2139
+ listChannelMessages: defineContract({
2140
+ method: 'GET',
2141
+ path: '/api/channels/:channelId/messages',
2142
+ params: z
2143
+ .object({
2144
+ channelId: ChannelIdSchema,
2145
+ limit: z.number().int().min(1).max(100),
2146
+ beforeOccurredAt: z.number().int().safe().nonnegative().optional(),
2147
+ beforeSourceId: NonEmptyStringSchema.optional(),
2148
+ })
2149
+ .strict()
2150
+ .refine(
2151
+ (value) => (value.beforeOccurredAt === undefined) === (value.beforeSourceId === undefined),
2152
+ '频道历史游标必须完整提供。',
2153
+ ),
2154
+ request: NoRequestBodySchema,
2155
+ response: z
2156
+ .object({ messages: z.array(HostSnapshotMessageSchema), hasMore: z.boolean(), cursor: HostSyncCursorSchema })
2157
+ .strict(),
2158
+ error: HostApiErrorSchema,
2159
+ }),
2160
+ sendChannelMessage: defineContract({
2161
+ method: 'POST',
2162
+ path: '/api/channels/:channelId/messages',
2163
+ params: channelParam,
2164
+ request: SendChannelMessageRequestSchema,
2165
+ response: z
2166
+ .object({
2167
+ inserted: z.boolean(),
2168
+ channelEventId: ChannelEventIdSchema.optional(),
2169
+ outboundIntentId: OutboundIntentIdSchema.optional(),
2170
+ })
2171
+ .strict(),
2172
+ error: HostApiErrorSchema,
2173
+ }),
2174
+ renameChannel: defineContract({
2175
+ invalidatesSnapshot: true,
2176
+ method: 'POST',
2177
+ path: '/api/channels/:channelId/display-name',
2178
+ params: channelParam,
2179
+ request: z.object({ displayName: z.string().trim().min(1).max(120) }).strict(),
2180
+ response: z.object({ channelId: ChannelIdSchema, displayName: z.string().trim().min(1).max(120) }).strict(),
2181
+ error: HostApiErrorSchema,
2182
+ }),
2183
+ getChannelRuntime: defineContract({
2184
+ method: 'GET',
2185
+ path: '/api/channels/:channelId/runtime',
2186
+ params: channelParam,
2187
+ request: NoRequestBodySchema,
2188
+ response: ChannelRuntimeProjectionSchema.extend({ cursor: HostSyncCursorSchema }),
2189
+ error: HostApiErrorSchema,
2190
+ }),
2191
+ getChannelToolCall: defineContract({
2192
+ method: 'GET',
2193
+ path: '/api/channels/:channelId/runtime/tools/:callId',
2194
+ params: z.object({ channelId: ChannelIdSchema, callId: z.string().trim().min(1).max(200) }).strict(),
2195
+ request: NoRequestBodySchema,
2196
+ // The full arguments and result of one tool call in the live session; the runtime projection only carries previews.
2197
+ // `available: false` when the session has left memory: the step keeps its preview, which is not an error.
2198
+ response: z
2199
+ .object({
2200
+ callId: z.string(),
2201
+ available: z.boolean(),
2202
+ name: z.string().optional(),
2203
+ input: z.string().optional(),
2204
+ result: z.string().optional(),
2205
+ inputTruncated: z.boolean(),
2206
+ resultTruncated: z.boolean(),
2207
+ })
2208
+ .strict(),
2209
+ error: HostApiErrorSchema,
2210
+ }),
2211
+ resetChannelContext: defineContract({
2212
+ invalidatesSnapshot: true,
2213
+ method: 'POST',
2214
+ path: '/api/channels/:channelId/context-reset',
2215
+ params: channelParam,
2216
+ request: z
2217
+ .object({
2218
+ mode: z.enum(['clear', 'compact']),
2219
+ expectedEpisodeId: EpisodeIdSchema,
2220
+ })
2221
+ .strict(),
2222
+ response: z
2223
+ .object({
2224
+ mode: z.enum(['clear', 'compact']),
2225
+ closedEpisodeId: EpisodeIdSchema,
2226
+ nextEpisodeId: EpisodeIdSchema.optional(),
2227
+ })
2228
+ .strict(),
2229
+ error: HostApiErrorSchema,
2230
+ }),
2231
+ createConnection: defineContract({
2232
+ invalidatesSnapshot: true,
2233
+ method: 'POST',
2234
+ path: '/api/connections',
2235
+ params: EmptyParamsSchema,
2236
+ request: z
2237
+ .object({
2238
+ adapterKey: NonEmptyStringSchema,
2239
+ alias: ConnectionAliasInputSchema.optional(),
2240
+ configuration: z.record(z.string(), JsonValueSchema).default({}),
2241
+ credentials: z.record(z.string(), z.string().max(16 * 1024)).default({}),
2242
+ })
2243
+ .strict(),
2244
+ response: z.object({ connectionId: ConnectionIdSchema, adapterKey: NonEmptyStringSchema }).strict(),
2245
+ error: HostApiErrorSchema,
2246
+ }),
2247
+ startConnectionLogin: defineContract({
2248
+ method: 'POST',
2249
+ path: '/api/connection-logins',
2250
+ params: EmptyParamsSchema,
2251
+ request: z
2252
+ .object({
2253
+ adapterKey: NonEmptyStringSchema,
2254
+ alias: ConnectionAliasInputSchema.optional(),
2255
+ connectionId: ConnectionIdSchema.optional(),
2256
+ })
2257
+ .strict(),
2258
+ response: z
2259
+ .object({
2260
+ loginId: NonEmptyStringSchema,
2261
+ adapterKey: NonEmptyStringSchema,
2262
+ status: z.enum(['pending', 'scanned', 'confirmed', 'expired', 'failed', 'cancelled']),
2263
+ qrCodeUrl: NonEmptyStringSchema,
2264
+ message: z.string().optional(),
2265
+ })
2266
+ .strict(),
2267
+ error: HostApiErrorSchema,
2268
+ }),
2269
+ getConnectionLogin: defineContract({
2270
+ method: 'GET',
2271
+ path: '/api/connection-logins/:loginId',
2272
+ params: z.object({ loginId: NonEmptyStringSchema }).strict(),
2273
+ request: NoRequestBodySchema,
2274
+ response: z
2275
+ .object({
2276
+ loginId: NonEmptyStringSchema,
2277
+ status: z.enum(['pending', 'scanned', 'confirmed', 'expired', 'failed', 'cancelled']),
2278
+ qrCodeUrl: NonEmptyStringSchema.optional(),
2279
+ connectionId: ConnectionIdSchema.optional(),
2280
+ adapterKey: NonEmptyStringSchema,
2281
+ message: z.string().optional(),
2282
+ })
2283
+ .strict(),
2284
+ error: HostApiErrorSchema,
2285
+ }),
2286
+ cancelConnectionLogin: defineContract({
2287
+ method: 'DELETE',
2288
+ path: '/api/connection-logins/:loginId',
2289
+ params: z.object({ loginId: NonEmptyStringSchema }).strict(),
2290
+ request: NoRequestBodySchema,
2291
+ response: z
2292
+ .object({
2293
+ loginId: NonEmptyStringSchema,
2294
+ status: z.literal('cancelled'),
2295
+ })
2296
+ .strict(),
2297
+ error: HostApiErrorSchema,
2298
+ }),
2299
+ updateConnectionAlias: defineContract({
2300
+ invalidatesSnapshot: true,
2301
+ method: 'POST',
2302
+ path: '/api/connections/:connectionId/alias',
2303
+ params: connectionParam,
2304
+ request: z.object({ alias: z.string().trim().max(80) }).strict(),
2305
+ response: z
2306
+ .object({
2307
+ connectionId: ConnectionIdSchema,
2308
+ alias: ConnectionAliasOutputSchema.optional(),
2309
+ })
2310
+ .strict(),
2311
+ error: HostApiErrorSchema,
2312
+ }),
2313
+ updateConnectionActivityTriggerDefaults: defineContract({
2314
+ invalidatesSnapshot: true,
2315
+ method: 'POST',
2316
+ path: '/api/connections/:connectionId/activity-trigger-defaults',
2317
+ params: connectionParam,
2318
+ request: z.object({ activityKeys: z.array(AdapterActivityKeySchema) }).strict(),
2319
+ response: z.object({ connectionId: ConnectionIdSchema, activityKeys: z.array(AdapterActivityKeySchema) }).strict(),
2320
+ error: HostApiErrorSchema,
2321
+ }),
2322
+ updateConnectionConfiguration: defineContract({
2323
+ invalidatesSnapshot: true,
2324
+ method: 'POST',
2325
+ path: '/api/connections/:connectionId/configuration',
2326
+ params: connectionParam,
2327
+ request: z.object({ configuration: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])) }).strict(),
2328
+ response: z
2329
+ .object({
2330
+ connectionId: ConnectionIdSchema,
2331
+ configuration: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
2332
+ })
2333
+ .strict(),
2334
+ error: HostApiErrorSchema,
2335
+ }),
2336
+ deleteConnection: defineContract({
2337
+ invalidatesSnapshot: true,
2338
+ method: 'DELETE',
2339
+ path: '/api/connections/:connectionId',
2340
+ params: connectionParam,
2341
+ request: z.object({ deleteChannelData: z.boolean() }).strict(),
2342
+ response: z.object({ connectionId: ConnectionIdSchema, archived: z.boolean() }).strict(),
2343
+ error: HostApiErrorSchema,
2344
+ }),
2345
+ restoreConnection: defineContract({
2346
+ invalidatesSnapshot: true,
2347
+ method: 'POST',
2348
+ path: '/api/connections/:connectionId/restore',
2349
+ params: connectionParam,
2350
+ request: NoRequestBodySchema,
2351
+ response: z.object({ connectionId: ConnectionIdSchema, restored: z.literal(true) }).strict(),
2352
+ error: HostApiErrorSchema,
2353
+ }),
2354
+ dshPlugins: defineContract({
2355
+ method: 'GET',
2356
+ path: '/api/dsh/plugins',
2357
+ params: EmptyParamsSchema,
2358
+ request: NoRequestBodySchema,
2359
+ response: z.object({ plugins: z.array(DshPluginCatalogEntrySchema) }).strict(),
2360
+ error: HostApiErrorSchema,
2361
+ }),
2362
+ inspectDshPluginTarball: defineContract({
2363
+ method: 'POST',
2364
+ timeoutMs: 300_000,
2365
+ path: '/api/dsh/plugin-installs/inspect-tarball',
2366
+ params: EmptyParamsSchema,
2367
+ request: BinaryUploadRequestSchema,
2368
+ encodeRequest: encodeBinaryUpload,
2369
+ response: DshPluginInstallInspectionSchema,
2370
+ error: HostApiErrorSchema,
2371
+ }),
2372
+ exportExtensionRevision: defineContract({
2373
+ method: 'GET',
2374
+ timeoutMs: 300_000,
2375
+ path: '/api/extensions/:extensionId/revisions/:revisionId/export',
2376
+ params: extensionRevisionParam,
2377
+ request: NoRequestBodySchema,
2378
+ responseFormat: 'bytes',
2379
+ response: z.instanceof(Uint8Array),
2380
+ error: HostApiErrorSchema,
2381
+ }),
2382
+ inspectDshPluginInstall: defineContract({
2383
+ timeoutMs: 300_000,
2384
+ method: 'POST',
2385
+ path: '/api/dsh/plugin-installs/inspect',
2386
+ params: EmptyParamsSchema,
2387
+ request: z.object({ spec: NonEmptyStringSchema.max(500), operationId: z.string().uuid().optional() }).strict(),
2388
+ response: DshPluginInstallInspectionSchema,
2389
+ error: HostApiErrorSchema,
2390
+ }),
2391
+ commitDshPluginInstall: defineContract({
2392
+ invalidatesSnapshot: true,
2393
+ timeoutMs: 300_000,
2394
+ method: 'POST',
2395
+ path: '/api/dsh/plugin-installs',
2396
+ params: EmptyParamsSchema,
2397
+ request: z
2398
+ .object({
2399
+ token: NonEmptyStringSchema,
2400
+ approvedBuilds: z.array(NonEmptyStringSchema).default([]),
2401
+ operationId: z.string().uuid().optional(),
2402
+ })
2403
+ .strict(),
2404
+ response: z.object({ packageId: DshPluginPackageIdSchema, operationId: z.string().uuid().optional() }).strict(),
2405
+ error: HostApiErrorSchema,
2406
+ }),
2407
+ inspectDshPluginEntryConfig: defineContract({
2408
+ method: 'POST',
2409
+ path: '/api/dsh/plugin-entries/:entryId/config/inspect',
2410
+ params: dshPluginEntryParam,
2411
+ request: NoRequestBodySchema,
2412
+ response: z.discriminatedUnion('mode', [
2413
+ z.object({ mode: z.literal('schema'), schema: JsonValueSchema }).strict(),
2414
+ z.object({ mode: z.literal('json') }).strict(),
2415
+ z.object({ mode: z.literal('incompatible'), reason: NonEmptyStringSchema }).strict(),
2416
+ ]),
2417
+ error: HostApiErrorSchema,
2418
+ }),
2419
+ activateDshPluginEntry: defineContract({
2420
+ method: 'PUT',
2421
+ path: '/api/dsh/plugin-entries/:entryId/activation',
2422
+ params: dshPluginEntryParam,
2423
+ request: z
2424
+ .object({
2425
+ target: z.enum(['host', 'agent']),
2426
+ agentId: AgentIdSchema.optional(),
2427
+ config: JsonValueSchema,
2428
+ permissionApproval: z
2429
+ .object({ permissionDigest: z.string().regex(/^[a-f0-9]{64}$/u) })
2430
+ .strict()
2431
+ .optional(),
2432
+ })
2433
+ .strict(),
2434
+ response: z.object({ targetKey: NonEmptyStringSchema }).strict(),
2435
+ error: HostApiErrorSchema,
2436
+ }),
2437
+ deactivateDshPluginEntry: defineContract({
2438
+ method: 'DELETE',
2439
+ path: '/api/dsh/plugin-entries/:entryId/activation',
2440
+ params: dshPluginEntryParam,
2441
+ request: z.object({ targetKey: NonEmptyStringSchema }).strict(),
2442
+ response: z.object({ disabled: z.literal(true) }).strict(),
2443
+ error: HostApiErrorSchema,
2444
+ }),
2445
+ removeDshPluginPackage: defineContract({
2446
+ invalidatesSnapshot: true,
2447
+ method: 'DELETE',
2448
+ path: '/api/dsh/plugin-installs/:packageId',
2449
+ params: dshPluginPackageParam,
2450
+ request: NoRequestBodySchema,
2451
+ response: z.object({ removed: z.literal(true) }).strict(),
2452
+ error: HostApiErrorSchema,
2453
+ }),
2454
+ inspectExtensionImport: defineContract({
2455
+ timeoutMs: 300_000,
2456
+ method: 'POST',
2457
+ path: '/api/extensions/imports/inspect',
2458
+ params: EmptyParamsSchema,
2459
+ request: BinaryUploadRequestSchema,
2460
+ encodeRequest: encodeBinaryUpload,
2461
+ response: ExtensionImportInspectionSchema,
2462
+ error: HostApiErrorSchema,
2463
+ }),
2464
+ /**
2465
+ * Management-edge endpoints: they exist only when the Server is reached through its management edge (a management
2466
+ * key is set); clients tell that from the edge's CSRF cookie.
2467
+ */
2468
+ listManagementDevices: defineContract({
2469
+ method: 'GET',
2470
+ path: '/api/management/devices',
2471
+ params: EmptyParamsSchema,
2472
+ request: NoRequestBodySchema,
2473
+ response: z.object({ devices: z.array(ManagementDeviceViewSchema) }).strict(),
2474
+ error: HostApiErrorSchema,
2475
+ }),
2476
+ revokeManagementDevice: defineContract({
2477
+ method: 'DELETE',
2478
+ path: '/api/management/devices/:deviceId',
2479
+ params: z.object({ deviceId: ManagementDeviceIdSchema }).strict(),
2480
+ request: NoRequestBodySchema,
2481
+ response: z.object({ revoked: z.boolean() }).strict(),
2482
+ error: HostApiErrorSchema,
2483
+ }),
2484
+ managementLogout: defineContract({
2485
+ method: 'DELETE',
2486
+ path: '/api/management/session',
2487
+ params: EmptyParamsSchema,
2488
+ request: NoRequestBodySchema,
2489
+ response: z.object({ authenticated: z.literal(false) }).strict(),
2490
+ error: HostApiErrorSchema,
2491
+ }),
2492
+ getCommunityStatus: defineContract({
2493
+ method: 'GET',
2494
+ path: '/api/community/status',
2495
+ params: EmptyParamsSchema,
2496
+ request: NoRequestBodySchema,
2497
+ response: CommunityStatusSchema,
2498
+ error: HostApiErrorSchema,
2499
+ }),
2500
+ startCommunityLogin: defineContract({
2501
+ method: 'POST',
2502
+ path: '/api/community/login',
2503
+ params: EmptyParamsSchema,
2504
+ /** 浏览器当前访问本实例使用的来源;社区授权完成后回到 `<returnOrigin>/community/callback`。 */
2505
+ request: z.object({ returnOrigin: z.string().url().max(300) }).strict(),
2506
+ response: z.object({ authorizeUrl: z.string().url() }).strict(),
2507
+ error: HostApiErrorSchema,
2508
+ }),
2509
+ communityLogout: defineContract({
2510
+ method: 'DELETE',
2511
+ path: '/api/community/login',
2512
+ params: EmptyParamsSchema,
2513
+ request: NoRequestBodySchema,
2514
+ response: CommunityStatusSchema,
2515
+ error: HostApiErrorSchema,
2516
+ }),
2517
+ listCommunityExtensions: defineContract({
2518
+ timeoutMs: 30_000,
2519
+ method: 'GET',
2520
+ path: '/api/community/extensions',
2521
+ params: z
2522
+ .object({
2523
+ query: z.string().trim().max(80).optional(),
2524
+ scope: z.enum(['agent', 'host-adapter', 'host-ui']).optional(),
2525
+ cursor: z.string().max(200).optional(),
2526
+ })
2527
+ .strict(),
2528
+ request: NoRequestBodySchema,
2529
+ response: z.object({ items: z.array(CommunityExtensionSummarySchema), nextCursor: z.string().nullable() }).strict(),
2530
+ error: HostApiErrorSchema,
2531
+ }),
2532
+ getCommunityExtension: defineContract({
2533
+ timeoutMs: 30_000,
2534
+ method: 'GET',
2535
+ path: '/api/community/extensions/:extensionId',
2536
+ params: z.object({ extensionId: ExtensionIdSchema }).strict(),
2537
+ request: NoRequestBodySchema,
2538
+ response: CommunityExtensionDetailSchema,
2539
+ error: HostApiErrorSchema,
2540
+ }),
2541
+ /** 下载社区发布并按包摘要校验,结果与本地文件导入的检查相同,再经 `commitExtensionImport` 确认。 */
2542
+ importCommunityRelease: defineContract({
2543
+ timeoutMs: 120_000,
2544
+ method: 'POST',
2545
+ path: '/api/community/releases/:releaseId/import',
2546
+ params: z.object({ releaseId: CommunityReleaseIdSchema }).strict(),
2547
+ request: NoRequestBodySchema,
2548
+ response: ExtensionImportInspectionSchema,
2549
+ error: HostApiErrorSchema,
2550
+ }),
2551
+ publishToCommunity: defineContract({
2552
+ timeoutMs: 120_000,
2553
+ method: 'POST',
2554
+ path: '/api/community/publish',
2555
+ params: EmptyParamsSchema,
2556
+ request: z
2557
+ .object({
2558
+ extensionId: ExtensionIdSchema,
2559
+ revisionId: ExtensionRevisionIdSchema,
2560
+ notes: z.string().max(2000),
2561
+ })
2562
+ .strict(),
2563
+ response: z
2564
+ .object({
2565
+ releaseId: CommunityReleaseIdSchema,
2566
+ reviewStatus: CommunityReviewStatusSchema,
2567
+ pageUrl: z.string().url(),
2568
+ reportUrl: z.string().url(),
2569
+ findings: z.array(z.object({ severity: z.string(), title: z.string() }).strict()),
2570
+ })
2571
+ .strict(),
2572
+ error: HostApiErrorSchema,
2573
+ }),
2574
+ testMcpServer: defineContract({
2575
+ timeoutMs: 30_000,
2576
+ method: 'POST',
2577
+ path: '/api/mcp-servers/test',
2578
+ params: EmptyParamsSchema,
2579
+ request: z.object({ server: McpServerFormSchema }).strict(),
2580
+ response: z
2581
+ .object({
2582
+ ok: z.boolean(),
2583
+ message: z.string(),
2584
+ serverName: z.string().optional(),
2585
+ tools: z.array(z.object({ name: z.string(), description: z.string().optional() }).strict()),
2586
+ })
2587
+ .strict(),
2588
+ error: HostApiErrorSchema,
2589
+ }),
2590
+ createMcpExtension: defineContract({
2591
+ invalidatesSnapshot: true,
2592
+ timeoutMs: 120_000,
2593
+ method: 'POST',
2594
+ path: '/api/mcp-servers',
2595
+ params: EmptyParamsSchema,
2596
+ request: z
2597
+ .object({
2598
+ displayName: z.string().trim().min(1).max(80),
2599
+ description: z.string().trim().max(500).default(''),
2600
+ server: McpServerFormSchema,
2601
+ })
2602
+ .strict(),
2603
+ response: z
2604
+ .object({
2605
+ extensionId: ExtensionIdSchema,
2606
+ revisionId: ExtensionRevisionIdSchema,
2607
+ /** Credential config fields created for the form's secret rows, by row name. */
2608
+ secretFields: z.array(z.object({ key: z.string(), name: z.string() }).strict()),
2609
+ })
2610
+ .strict(),
2611
+ error: HostApiErrorSchema,
2612
+ }),
2613
+ commitExtensionImport: defineContract({
2614
+ invalidatesSnapshot: true,
2615
+ timeoutMs: 300_000,
2616
+ method: 'POST',
2617
+ path: '/api/extensions/imports/:token/commit',
2618
+ params: z.object({ token: NonEmptyStringSchema }).strict(),
2619
+ request: z.object({ localSlug: z.string().trim().min(3).max(64).optional() }).strict(),
2620
+ response: z
2621
+ .object({ extensionId: ExtensionIdSchema, revisionId: ExtensionRevisionIdSchema, idempotent: z.boolean() })
2622
+ .strict(),
2623
+ error: HostApiErrorSchema,
2624
+ }),
2625
+ dshSettings: defineContract({
2626
+ method: 'GET',
2627
+ path: '/api/dsh/settings',
2628
+ params: EmptyParamsSchema,
2629
+ request: NoRequestBodySchema,
2630
+ response: z.object({ namespaces: z.array(DshSettingsNamespaceSchema) }).strict(),
2631
+ error: HostApiErrorSchema,
2632
+ }),
2633
+ dshSettingsMutate: defineContract({
2634
+ invalidatesSnapshot: true,
2635
+ method: 'POST',
2636
+ path: '/api/dsh/settings/:namespace/mutate',
2637
+ params: dshSettingsParam,
2638
+ request: DshSettingsMutationRequestSchema,
2639
+ response: DshSettingsNamespaceSchema,
2640
+ error: HostApiErrorSchema,
2641
+ }),
2642
+ dshCredentialsDescribe: defineContract({
2643
+ method: 'POST',
2644
+ path: '/api/dsh/credentials/describe',
2645
+ params: EmptyParamsSchema,
2646
+ request: z.object({ refs: z.array(DshCredentialRefSchema).max(64) }).strict(),
2647
+ response: z.object({ credentials: z.record(z.string(), DshCredentialViewSchema) }).strict(),
2648
+ error: HostApiErrorSchema,
2649
+ }),
2650
+ dshCredentialSet: defineContract({
2651
+ invalidatesSnapshot: true,
2652
+ method: 'PUT',
2653
+ path: '/api/dsh/credentials/:ref',
2654
+ params: dshCredentialParam,
2655
+ request: z
2656
+ .object({
2657
+ value: z
2658
+ .string()
2659
+ .min(1)
2660
+ .max(64 * 1024),
2661
+ })
2662
+ .strict(),
2663
+ response: DshCredentialViewSchema,
2664
+ error: HostApiErrorSchema,
2665
+ }),
2666
+ dshCredentialUnset: defineContract({
2667
+ invalidatesSnapshot: true,
2668
+ method: 'DELETE',
2669
+ path: '/api/dsh/credentials/:ref',
2670
+ params: dshCredentialParam,
2671
+ request: NoRequestBodySchema,
2672
+ response: DshCredentialViewSchema,
2673
+ error: HostApiErrorSchema,
2674
+ }),
2675
+ llmProviders: defineContract({
2676
+ method: 'GET',
2677
+ path: '/api/llm/providers',
2678
+ params: EmptyParamsSchema,
2679
+ request: NoRequestBodySchema,
2680
+ response: LlmProviderSettingsSchema,
2681
+ error: HostApiErrorSchema,
2682
+ }),
2683
+ llmDiscoverModels: defineContract({
2684
+ method: 'POST',
2685
+ path: '/api/llm/discover-models',
2686
+ params: EmptyParamsSchema,
2687
+ request: z
2688
+ .object({
2689
+ provider: NonEmptyStringSchema.optional(),
2690
+ settingsNs: NonEmptyStringSchema.optional(),
2691
+ baseURL: z.url().optional(),
2692
+ api: NonEmptyStringSchema.optional(),
2693
+ apiKey: z
2694
+ .string()
2695
+ .min(1)
2696
+ .max(64 * 1024)
2697
+ .optional(),
2698
+ })
2699
+ .strict(),
2700
+ response: z.object({ models: z.array(LlmDiscoveredModelSchema) }).strict(),
2701
+ error: HostApiErrorSchema,
2702
+ }),
2703
+ llmTestProvider: defineContract({
2704
+ method: 'POST',
2705
+ path: '/api/llm/test-provider',
2706
+ params: EmptyParamsSchema,
2707
+ request: z
2708
+ .object({
2709
+ provider: NonEmptyStringSchema,
2710
+ model: NonEmptyStringSchema,
2711
+ settingsNs: NonEmptyStringSchema.optional(),
2712
+ apiKey: z
2713
+ .string()
2714
+ .min(1)
2715
+ .max(64 * 1024)
2716
+ .optional(),
2717
+ baseURL: z.url().optional(),
2718
+ api: NonEmptyStringSchema.optional(),
2719
+ models: z.array(LlmDiscoveredModelSchema).optional(),
2720
+ })
2721
+ .strict(),
2722
+ response: z.object({ provider: NonEmptyStringSchema, model: NonEmptyStringSchema }).strict(),
2723
+ error: HostApiErrorSchema,
2724
+ }),
2725
+ llmProviderRemovalImpact: defineContract({
2726
+ method: 'GET',
2727
+ path: '/api/llm/providers/:provider/removal-impact',
2728
+ params: llmProviderParam,
2729
+ request: NoRequestBodySchema,
2730
+ response: z
2731
+ .object({
2732
+ provider: NonEmptyStringSchema,
2733
+ displayName: NonEmptyStringSchema,
2734
+ declared: z.boolean(),
2735
+ expectedRevision: z.number().int().nonnegative(),
2736
+ models: z.array(NonEmptyStringSchema),
2737
+ blockedReason: z.string(),
2738
+ references: z.array(
2739
+ z
2740
+ .object({
2741
+ agentId: AgentIdSchema,
2742
+ displayName: NonEmptyStringSchema,
2743
+ model: NonEmptyStringSchema,
2744
+ role: z.enum(['primary', 'vision']),
2745
+ scope: z.enum(['configuration', 'context']),
2746
+ channelId: ChannelIdSchema.optional(),
2747
+ channelName: z.string().optional(),
2748
+ episodeStatus: z.enum(['opening', 'active']).optional(),
2749
+ })
2750
+ .strict(),
2751
+ ),
2752
+ })
2753
+ .strict(),
2754
+ error: HostApiErrorSchema,
2755
+ }),
2756
+ llmRemoveProvider: defineContract({
2757
+ invalidatesSnapshot: true,
2758
+ method: 'DELETE',
2759
+ path: '/api/llm/providers/:provider',
2760
+ params: llmProviderParam,
2761
+ request: z.object({ expectedRevision: z.number().int().nonnegative() }).strict(),
2762
+ response: LlmProviderSettingsSchema,
2763
+ error: HostApiErrorSchema,
2764
+ }),
2765
+ llmRestoreProviderModels: defineContract({
2766
+ invalidatesSnapshot: true,
2767
+ method: 'POST',
2768
+ path: '/api/llm/providers/:provider/restore-models',
2769
+ params: llmProviderParam,
2770
+ request: z.object({ expectedRevision: z.number().int().nonnegative() }).strict(),
2771
+ response: LlmProviderSettingsSchema,
2772
+ error: HostApiErrorSchema,
2773
+ }),
2774
+ llmSaveProvider: defineContract({
2775
+ invalidatesSnapshot: true,
2776
+ method: 'POST',
2777
+ path: '/api/llm/providers/:provider',
2778
+ params: llmProviderParam,
2779
+ request: z
2780
+ .object({
2781
+ expectedRevision: z.number().int().nonnegative(),
2782
+ apiKey: z
2783
+ .string()
2784
+ .min(1)
2785
+ .max(64 * 1024)
2786
+ .optional(),
2787
+ displayName: z.string().trim().min(1).max(120).optional(),
2788
+ baseURL: z.url().optional(),
2789
+ api: NonEmptyStringSchema.optional(),
2790
+ models: z.array(LlmDiscoveredModelSchema).optional(),
2791
+ })
2792
+ .strict(),
2793
+ response: LlmProviderSettingsSchema,
2794
+ error: HostApiErrorSchema,
2795
+ }),
2796
+ testConnection: defineContract({
2797
+ invalidatesSnapshot: true,
2798
+ method: 'POST',
2799
+ path: '/api/connections/:connectionId/test',
2800
+ params: connectionParam,
2801
+ request: z.object({ direction: z.enum(['send', 'receive']), channelId: ChannelIdSchema.optional() }).strict(),
2802
+ response: ConnectionTestResultSchema,
2803
+ error: HostApiErrorSchema,
2804
+ }),
2805
+ dynamicInventory: defineContract({
2806
+ method: 'POST',
2807
+ path: '/api/dynamic/:agentId/inventory',
2808
+ params: agentParam,
2809
+ request: z.object({ episodeId: EpisodeIdSchema }).strict(),
2810
+ response: z.object({ rows: z.array(DynamicInventoryRowSchema) }).strict(),
2811
+ error: HostApiErrorSchema,
2812
+ }),
2813
+ dynamicApprove: defineContract({
2814
+ invalidatesSnapshot: true,
2815
+ method: 'POST',
2816
+ path: '/api/dynamic/:agentId/approve',
2817
+ params: agentParam,
2818
+ request: z
2819
+ .object({ episodeId: EpisodeIdSchema, requestId: DynamicIdSchema, pluginRunId: DynamicIdSchema.optional() })
2820
+ .strict(),
2821
+ response: z.object({ accepted: z.boolean() }).strict(),
2822
+ error: HostApiErrorSchema,
2823
+ }),
2824
+ dynamicDecline: defineContract({
2825
+ invalidatesSnapshot: true,
2826
+ method: 'POST',
2827
+ path: '/api/dynamic/:agentId/decline',
2828
+ params: agentParam,
2829
+ request: z
2830
+ .object({ episodeId: EpisodeIdSchema, requestId: DynamicIdSchema, pluginRunId: DynamicIdSchema.optional() })
2831
+ .strict(),
2832
+ response: z.object({ accepted: z.boolean() }).strict(),
2833
+ error: HostApiErrorSchema,
2834
+ }),
2835
+ dynamicInvoke: defineContract({
2836
+ method: 'POST',
2837
+ path: '/api/dynamic/:agentId/invoke',
2838
+ params: agentParam,
2839
+ request: z
2840
+ .object({
2841
+ episodeId: EpisodeIdSchema,
2842
+ pluginId: DynamicIdSchema,
2843
+ pluginRunId: DynamicIdSchema,
2844
+ method: NonEmptyStringSchema,
2845
+ args: JsonValueSchema.optional(),
2846
+ })
2847
+ .strict(),
2848
+ response: z.discriminatedUnion('ok', [
2849
+ z.object({ ok: z.literal(true), value: JsonValueSchema }).strict(),
2850
+ z.object({ ok: z.literal(false), message: z.string() }).strict(),
2851
+ ]),
2852
+ error: HostApiErrorSchema,
2853
+ }),
2854
+ dynamicRunHostHalf: defineContract({
2855
+ method: 'POST',
2856
+ path: '/api/dynamic/:agentId/run-host-half',
2857
+ params: agentParam,
2858
+ request: z
2859
+ .object({
2860
+ episodeId: EpisodeIdSchema,
2861
+ pluginId: DynamicIdSchema,
2862
+ packageId: DynamicIdSchema,
2863
+ mode: z.enum(['run', 'update']),
2864
+ requestId: DynamicIdSchema.nullable().optional(),
2865
+ approveFutureVersions: z.boolean().default(false),
2866
+ })
2867
+ .strict(),
2868
+ response: z.discriminatedUnion('ok', [
2869
+ z
2870
+ .object({
2871
+ ok: z.literal(true),
2872
+ pluginId: DynamicIdSchema,
2873
+ packageId: DynamicIdSchema,
2874
+ pluginRunId: DynamicIdSchema,
2875
+ waitingFor: z.array(z.string()),
2876
+ startedHere: z.boolean(),
2877
+ })
2878
+ .strict(),
2879
+ DynamicErrorDetailsSchema.extend({ ok: z.literal(false) }),
2880
+ ]),
2881
+ error: HostApiErrorSchema,
2882
+ }),
2883
+ dynamicSettleUserRun: defineContract({
2884
+ method: 'POST',
2885
+ path: '/api/dynamic/:agentId/settle-user-run',
2886
+ params: agentParam,
2887
+ request: z
2888
+ .object({ episodeId: EpisodeIdSchema, pluginId: DynamicIdSchema, resolution: DynamicRunResolutionSchema })
2889
+ .strict(),
2890
+ response: DynamicRunResponseSchema,
2891
+ error: HostApiErrorSchema,
2892
+ }),
2893
+ dynamicGetClientCode: defineContract({
2894
+ method: 'POST',
2895
+ path: '/api/dynamic/:agentId/get-client-code',
2896
+ params: agentParam,
2897
+ request: z.object({ episodeId: EpisodeIdSchema, pluginId: DynamicIdSchema, pluginRunId: DynamicIdSchema }).strict(),
2898
+ response: z
2899
+ .object({
2900
+ code: z.string(),
2901
+ name: z.string(),
2902
+ pluginId: DynamicIdSchema,
2903
+ packageId: DynamicIdSchema,
2904
+ pluginRunId: DynamicIdSchema,
2905
+ /** What the candidate declared; preview data hooks enforce exactly what the saved Revision will. */
2906
+ permissions: HostUiPermissionDeclarationSchema.default({ permissions: [], networkOrigins: [] }),
2907
+ })
2908
+ .strict(),
2909
+ error: HostApiErrorSchema,
2910
+ }),
2911
+ dynamicReportRenderFailure: defineContract({
2912
+ method: 'POST',
2913
+ path: '/api/dynamic/:agentId/report-render-failure',
2914
+ params: agentParam,
2915
+ request: z
2916
+ .object({
2917
+ episodeId: EpisodeIdSchema,
2918
+ pluginId: DynamicIdSchema,
2919
+ pluginRunId: DynamicIdSchema,
2920
+ failure: DynamicRenderFailureSchema,
2921
+ })
2922
+ .strict(),
2923
+ response: z.object({ ok: z.literal(true) }).strict(),
2924
+ error: HostApiErrorSchema,
2925
+ }),
2926
+ dynamicReportGuardFailure: defineContract({
2927
+ method: 'POST',
2928
+ path: '/api/dynamic/:agentId/report-guard-failure',
2929
+ params: agentParam,
2930
+ request: z
2931
+ .object({
2932
+ episodeId: EpisodeIdSchema,
2933
+ pluginId: DynamicIdSchema,
2934
+ pluginRunId: DynamicIdSchema,
2935
+ message: z.string().min(1).max(4096),
2936
+ stack: z
2937
+ .string()
2938
+ .max(16 * 1024)
2939
+ .optional(),
2940
+ })
2941
+ .strict(),
2942
+ response: z.object({ ok: z.literal(true) }).strict(),
2943
+ error: HostApiErrorSchema,
2944
+ }),
2945
+ dynamicReportClientVerification: defineContract({
2946
+ method: 'POST',
2947
+ path: '/api/dynamic/:agentId/report-client-verification',
2948
+ params: agentParam,
2949
+ request: z
2950
+ .object({
2951
+ episodeId: EpisodeIdSchema,
2952
+ pluginId: DynamicIdSchema,
2953
+ packageId: DynamicIdSchema,
2954
+ pluginRunId: DynamicIdSchema,
2955
+ renderedPanels: z.array(PanelContributionSchema).max(16).default([]),
2956
+ renderedToolViews: z.array(z.string().trim().min(1).max(64)).max(32).default([]),
2957
+ renderedMessageRenderers: z.array(RichKindSchema).max(32).default([]),
2958
+ renderedPages: z.array(HostPageContributionSchema).max(8).default([]),
2959
+ usedUiComponents: z
2960
+ .array(HostUiKitComponentNameSchema)
2961
+ .max(HostUiKitComponentNameSchema.options.length)
2962
+ .default([]),
2963
+ pageGeometry: z.array(HostUiPageGeometryEvidenceSchema).max(8).default([]),
2964
+ navigationEntries: z.array(z.string().trim().min(1).max(64)).max(8).default([]),
2965
+ })
2966
+ .strict()
2967
+ .refine(
2968
+ (value) =>
2969
+ value.renderedPanels.length > 0 ||
2970
+ value.renderedToolViews.length > 0 ||
2971
+ value.renderedMessageRenderers.length > 0 ||
2972
+ value.renderedPages.length > 0,
2973
+ {
2974
+ message: '动态 Client 必须真实渲染至少一个面板、工具视图、富消息渲染器或页面。',
2975
+ },
2976
+ ),
2977
+ response: z.object({ ok: z.literal(true) }).strict(),
2978
+ error: HostApiErrorSchema,
2979
+ }),
2980
+ saveExtensionFromDynamic: defineContract({
2981
+ invalidatesSnapshot: true,
2982
+ timeoutMs: 300_000,
2983
+ method: 'POST',
2984
+ path: '/api/extensions/save-from-dynamic',
2985
+ params: EmptyParamsSchema,
2986
+ request: z.union([
2987
+ z
2988
+ .object({
2989
+ taskId: AuthoringTaskIdSchema,
2990
+ attemptId: AuthoringAttemptIdSchema,
2991
+ displayName: z.string().trim().min(1).max(80),
2992
+ slug: z
2993
+ .string()
2994
+ .trim()
2995
+ .regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/u),
2996
+ description: z.string().max(500).default(''),
2997
+ targetExtensionId: ExtensionIdSchema.optional(),
2998
+ })
2999
+ .strict(),
3000
+ z
3001
+ .object({
3002
+ agentId: AgentIdSchema,
3003
+ episodeId: EpisodeIdSchema,
3004
+ pluginId: DynamicIdSchema,
3005
+ packageId: DynamicIdSchema,
3006
+ displayName: z.string().trim().min(1).max(80),
3007
+ slug: z
3008
+ .string()
3009
+ .trim()
3010
+ .regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/u),
3011
+ description: z.string().max(500).default(''),
3012
+ targetExtensionId: ExtensionIdSchema.optional(),
3013
+ })
3014
+ .strict(),
3015
+ ]),
3016
+ response: z
3017
+ .object({
3018
+ extensionId: ExtensionIdSchema,
3019
+ revisionId: ExtensionRevisionIdSchema,
3020
+ activation: z.literal('inactive'),
3021
+ installation: z.literal('uninstalled').optional(),
3022
+ })
3023
+ .strict(),
3024
+ error: HostApiErrorSchema,
3025
+ }),
3026
+ extensionClientCall: defineContract({
3027
+ method: 'POST',
3028
+ path: '/api/extensions/:extensionId/revisions/:revisionId/call',
3029
+ params: extensionRevisionParam,
3030
+ request: z
3031
+ .object({ agentId: AgentIdSchema, method: NonEmptyStringSchema, input: JsonValueSchema.optional() })
3032
+ .strict(),
3033
+ response: z.object({ value: JsonValueSchema }).strict(),
3034
+ error: HostApiErrorSchema,
3035
+ }),
3036
+ extensionClientDiagnostic: defineContract({
3037
+ method: 'POST',
3038
+ path: '/api/extensions/:extensionId/revisions/:revisionId/client-diagnostic',
3039
+ params: extensionRevisionParam,
3040
+ request: z
3041
+ .object({
3042
+ agentId: AgentIdSchema,
3043
+ status: z.enum(['loaded', 'failed']),
3044
+ message: z.string().max(4096).optional(),
3045
+ })
3046
+ .strict(),
3047
+ response: z.object({ accepted: z.literal(true) }).strict(),
3048
+ error: HostApiErrorSchema,
3049
+ }),
3050
+ hostExtensionClientDiagnostic: defineContract({
3051
+ method: 'POST',
3052
+ path: '/api/extensions/:extensionId/revisions/:revisionId/host-client-diagnostic',
3053
+ params: extensionRevisionParam,
3054
+ request: z.object({ status: z.enum(['loaded', 'failed']), message: z.string().max(4096).optional() }).strict(),
3055
+ response: z.object({ accepted: z.literal(true) }).strict(),
3056
+ error: HostApiErrorSchema,
3057
+ }),
3058
+ activateExtension: defineContract({
3059
+ invalidatesSnapshot: true,
3060
+ method: 'POST',
3061
+ path: '/api/agents/:agentId/extensions/:extensionId/activation',
3062
+ params: agentExtensionParam,
3063
+ request: z
3064
+ .object({
3065
+ revisionId: ExtensionRevisionIdSchema,
3066
+ permissionApproval: z
3067
+ .object({ permissionDigest: z.string().regex(/^[a-f0-9]{64}$/u) })
3068
+ .strict()
3069
+ .optional(),
3070
+ })
3071
+ .strict(),
3072
+ response: z
3073
+ .object({
3074
+ activation: z
3075
+ .object({
3076
+ agentId: AgentIdSchema,
3077
+ extensionId: ExtensionIdSchema,
3078
+ extensionRevisionId: ExtensionRevisionIdSchema,
3079
+ config: JsonValueSchema,
3080
+ configuredSecrets: z.array(z.string()).optional(),
3081
+ activatedAt: z.number().int().safe().nonnegative(),
3082
+ })
3083
+ .strict(),
3084
+ })
3085
+ .strict(),
3086
+ error: HostApiErrorSchema,
3087
+ }),
3088
+ deactivateExtension: defineContract({
3089
+ invalidatesSnapshot: true,
3090
+ method: 'DELETE',
3091
+ path: '/api/agents/:agentId/extensions/:extensionId/activation',
3092
+ params: agentExtensionParam,
3093
+ request: NoRequestBodySchema,
3094
+ response: z.object({ disabled: z.literal(true) }).strict(),
3095
+ error: HostApiErrorSchema,
3096
+ }),
3097
+ updateExtensionActivationConfig: defineContract({
3098
+ invalidatesSnapshot: true,
3099
+ method: 'PUT',
3100
+ path: '/api/agents/:agentId/extensions/:extensionId/activation/config',
3101
+ params: agentExtensionParam,
3102
+ request: z
3103
+ .object({
3104
+ config: JsonValueSchema,
3105
+ /** Write-only credential drafts keyed by secret field; an absent or empty draft keeps the stored one. */
3106
+ secrets: z.record(z.string(), z.string().max(8192)).optional(),
3107
+ })
3108
+ .strict(),
3109
+ response: z.object({ config: JsonValueSchema, configuredSecrets: z.array(z.string()).optional() }).strict(),
3110
+ error: HostApiErrorSchema,
3111
+ }),
3112
+ updateHostExtensionConfig: defineContract({
3113
+ invalidatesSnapshot: true,
3114
+ timeoutMs: 300_000,
3115
+ method: 'PUT',
3116
+ path: '/api/extensions/:extensionId/installation/config',
3117
+ params: extensionParam,
3118
+ request: z.object({ config: JsonValueSchema }).strict(),
3119
+ response: z.object({ config: JsonValueSchema }).strict(),
3120
+ error: HostApiErrorSchema,
3121
+ }),
3122
+ installHostExtension: defineContract({
3123
+ invalidatesSnapshot: true,
3124
+ timeoutMs: 300_000,
3125
+ method: 'PUT',
3126
+ path: '/api/extensions/:extensionId/installation',
3127
+ params: extensionParam,
3128
+ request: z
3129
+ .object({
3130
+ revisionId: ExtensionRevisionIdSchema,
3131
+ permissionApproval: z
3132
+ .object({ permissionDigest: z.string().regex(/^[a-f0-9]{64}$/u) })
3133
+ .strict()
3134
+ .optional(),
3135
+ })
3136
+ .strict(),
3137
+ response: z
3138
+ .object({
3139
+ installation: z
3140
+ .object({
3141
+ extensionId: ExtensionIdSchema,
3142
+ extensionRevisionId: ExtensionRevisionIdSchema,
3143
+ installedAt: z.number().int().safe().nonnegative(),
3144
+ config: JsonValueSchema,
3145
+ })
3146
+ .strict(),
3147
+ })
3148
+ .strict(),
3149
+ error: HostApiErrorSchema,
3150
+ }),
3151
+ uninstallHostExtension: defineContract({
3152
+ invalidatesSnapshot: true,
3153
+ timeoutMs: 300_000,
3154
+ method: 'DELETE',
3155
+ path: '/api/extensions/:extensionId/installation',
3156
+ params: extensionParam,
3157
+ request: NoRequestBodySchema,
3158
+ response: z.object({ uninstalled: z.literal(true) }).strict(),
3159
+ error: HostApiErrorSchema,
3160
+ }),
3161
+ updateHostUiPagePreferences: defineContract({
3162
+ invalidatesSnapshot: true,
3163
+ method: 'PUT',
3164
+ path: '/api/host-ui/page-preferences',
3165
+ params: EmptyParamsSchema,
3166
+ request: z
3167
+ .object({
3168
+ expectedRevision: z.number().int().nonnegative(),
3169
+ entries: z.array(z.object({ pageInstanceId: HostUiPageInstanceIdSchema, visible: z.boolean() }).strict()),
3170
+ })
3171
+ .strict(),
3172
+ response: z.object({ revision: z.number().int().positive() }).strict(),
3173
+ error: HostApiErrorSchema,
3174
+ }),
3175
+ callHostUiPage: defineContract({
3176
+ method: 'POST',
3177
+ path: '/api/host-ui/pages/:pageInstanceId/call',
3178
+ params: z.object({ pageInstanceId: HostUiPageInstanceIdSchema }).strict(),
3179
+ request: z.object({ method: NonEmptyStringSchema, input: JsonValueSchema.default({}) }).strict(),
3180
+ response: z.object({ value: JsonValueSchema }).strict(),
3181
+ error: HostApiErrorSchema,
3182
+ }),
3183
+ reportHostUiPageDiagnostic: defineContract({
3184
+ method: 'POST',
3185
+ path: '/api/host-ui/pages/:pageInstanceId/diagnostic',
3186
+ params: z.object({ pageInstanceId: HostUiPageInstanceIdSchema }).strict(),
3187
+ request: z
3188
+ .object({
3189
+ status: z.enum(['ready', 'load-failed', 'navigation-failed', 'rpc-failed']),
3190
+ message: z.string().max(2000).optional(),
3191
+ })
3192
+ .strict(),
3193
+ response: z.object({ recorded: z.literal(true) }).strict(),
3194
+ error: HostApiErrorSchema,
3195
+ }),
3196
+ markChannelRead: defineContract({
3197
+ method: 'POST',
3198
+ path: '/api/channels/:channelId/read',
3199
+ params: channelParam,
3200
+ request: z
3201
+ .object({
3202
+ /** Defaults to the newest inbound fact visible now. */
3203
+ upTo: z
3204
+ .object({ occurredAt: z.number().int().safe().nonnegative(), sourceId: ChannelEventIdSchema })
3205
+ .strict()
3206
+ .optional(),
3207
+ })
3208
+ .strict(),
3209
+ response: z.object({ channelId: ChannelIdSchema, activity: ChannelActivitySummarySchema }).strict(),
3210
+ error: HostApiErrorSchema,
3211
+ }),
3212
+ listAttention: defineContract({
3213
+ method: 'GET',
3214
+ path: '/api/attention',
3215
+ params: EmptyParamsSchema,
3216
+ request: NoRequestBodySchema,
3217
+ response: AttentionListSchema,
3218
+ error: HostApiErrorSchema,
3219
+ }),
3220
+ dismissAttention: defineContract({
3221
+ method: 'POST',
3222
+ path: '/api/attention/:attentionId/dismiss',
3223
+ params: z.object({ attentionId: z.string().min(1).max(200) }).strict(),
3224
+ request: NoRequestBodySchema,
3225
+ response: z.object({ dismissed: z.literal(true), revision: z.string().min(1) }).strict(),
3226
+ error: HostApiErrorSchema,
3227
+ }),
3228
+ getChannelPending: defineContract({
3229
+ method: 'GET',
3230
+ path: '/api/channels/:channelId/pending',
3231
+ params: channelParam,
3232
+ request: NoRequestBodySchema,
3233
+ response: ChannelPendingContextSchema,
3234
+ error: HostApiErrorSchema,
3235
+ }),
3236
+ stopChannelTask: defineContract({
3237
+ method: 'POST',
3238
+ path: '/api/channels/:channelId/stop',
3239
+ params: channelParam,
3240
+ request: z.object({ expectedEpisodeId: EpisodeIdSchema.optional() }).strict(),
3241
+ response: z
3242
+ .object({
3243
+ result: z.enum(['stopped', 'idle']),
3244
+ episodeId: EpisodeIdSchema.optional(),
3245
+ /** Admitted messages that were not yet consumed and remain queued for the next turn. */
3246
+ retainedPending: z.number().int().nonnegative(),
3247
+ })
3248
+ .strict(),
3249
+ error: HostApiErrorSchema,
3250
+ }),
3251
+ resolveOutbound: defineContract({
3252
+ method: 'POST',
3253
+ path: '/api/outbound/:outboundId/resolve',
3254
+ params: z.object({ outboundId: OutboundIntentIdSchema }).strict(),
3255
+ request: z.object({ action: z.enum(['retry', 'confirm-delivered']) }).strict(),
3256
+ response: z
3257
+ .object({
3258
+ outboundId: OutboundIntentIdSchema,
3259
+ action: z.enum(['retry', 'confirm-delivered']),
3260
+ deliveryState: z.enum(['planned', 'sending', 'sent', 'partially-sent', 'failed', 'unknown']),
3261
+ resolvedAt: z.number().int().safe().nonnegative(),
3262
+ })
3263
+ .strict(),
3264
+ error: HostApiErrorSchema,
3265
+ }),
3266
+ getChannelActivity: defineContract({
3267
+ method: 'GET',
3268
+ path: '/api/activity',
3269
+ params: ActivityQuerySchema,
3270
+ request: NoRequestBodySchema,
3271
+ response: ChannelActivitySeriesSchema,
3272
+ error: HostApiErrorSchema,
3273
+ }),
3274
+ listAgentRevisions: defineContract({
3275
+ method: 'GET',
3276
+ path: '/api/agents/:agentId/revisions',
3277
+ params: agentParam,
3278
+ request: NoRequestBodySchema,
3279
+ response: AgentRevisionHistorySchema,
3280
+ error: HostApiErrorSchema,
3281
+ }),
3282
+ restoreAgentRevision: defineContract({
3283
+ invalidatesSnapshot: true,
3284
+ method: 'POST',
3285
+ path: '/api/agents/:agentId/revisions/:revisionId/restore',
3286
+ params: agentRevisionParam,
3287
+ request: z.object({ expectedCurrentRevisionId: AgentRevisionIdSchema }).strict(),
3288
+ response: z.object({ currentRevisionId: AgentRevisionIdSchema }).strict(),
3289
+ error: HostApiErrorSchema,
3290
+ }),
3291
+ updateAgentAppearance: defineContract({
3292
+ invalidatesSnapshot: true,
3293
+ method: 'PATCH',
3294
+ path: '/api/agents/:agentId/appearance',
3295
+ params: agentParam,
3296
+ request: z
3297
+ .object({
3298
+ hue: z.number().int().min(0).max(359).nullable().optional(),
3299
+ /** `null` removes the avatar; uploading uses `uploadAgentAvatar`. */
3300
+ avatarAssetId: z.null().optional(),
3301
+ })
3302
+ .strict(),
3303
+ response: z.object({ agentId: AgentIdSchema, appearance: AgentAppearanceSchema }).strict(),
3304
+ error: HostApiErrorSchema,
3305
+ }),
3306
+ uploadAgentAvatar: defineContract({
3307
+ invalidatesSnapshot: true,
3308
+ method: 'POST',
3309
+ path: '/api/agents/:agentId/avatar',
3310
+ params: agentParam,
3311
+ request: BinaryUploadRequestSchema,
3312
+ encodeRequest: encodeBinaryUpload,
3313
+ response: z.object({ agentId: AgentIdSchema, appearance: AgentAppearanceSchema }).strict(),
3314
+ error: HostApiErrorSchema,
3315
+ }),
3316
+ deleteLocalExtension: defineContract({
3317
+ invalidatesSnapshot: true,
3318
+ method: 'DELETE',
3319
+ path: '/api/extensions/:extensionId',
3320
+ params: extensionParam,
3321
+ request: NoRequestBodySchema,
3322
+ response: z.object({ deleted: z.literal(true) }).strict(),
3323
+ error: HostApiErrorSchema,
3324
+ }),
3325
+ } as const
3326
+
3327
+ export type HostApiContractName = keyof typeof HostApiContracts
3328
+ export type HostApiParams<Name extends HostApiContractName> = z.input<(typeof HostApiContracts)[Name]['params']>
3329
+ export type HostApiRequest<Name extends HostApiContractName> = z.input<(typeof HostApiContracts)[Name]['request']>
3330
+ export type HostApiResponse<Name extends HostApiContractName> = z.output<(typeof HostApiContracts)[Name]['response']>
3331
+ export type HostApiContractParams<Contract> = Contract extends {
3332
+ readonly params: z.ZodType<unknown, infer Input>
3333
+ }
3334
+ ? Input
3335
+ : never
3336
+ export type HostApiContractRequest<Contract> = Contract extends {
3337
+ readonly request: z.ZodType<unknown, infer Input>
3338
+ }
3339
+ ? Input
3340
+ : never
3341
+ export type HostApiContractResponse<Contract> = Contract extends {
3342
+ readonly response: z.ZodType<infer Output, unknown>
3343
+ }
3344
+ ? Output
3345
+ : never
3346
+
3347
+ const pathParameterPattern = /:([A-Za-z][A-Za-z0-9]*)/gu
3348
+
3349
+ /** Build the transport URL only after all path/query parameters pass the shared schema. */
3350
+ const buildValidatedHostApiPath = (
3351
+ contract: { readonly path: string; readonly params: AnySchema },
3352
+ input: unknown,
3353
+ ): string => {
3354
+ const params = z.record(z.string(), z.unknown()).parse(contract.params.parse(input))
3355
+ const consumed = new Set<string>()
3356
+ const pathname = contract.path.replace(pathParameterPattern, (_placeholder, key: string) => {
3357
+ const value = params[key]
3358
+ if (typeof value !== 'string') throw new TypeError(`Host API path parameter ${key} must be a string.`)
3359
+ consumed.add(key)
3360
+ return encodeURIComponent(value)
3361
+ })
3362
+ const query = new URLSearchParams()
3363
+ for (const [key, value] of Object.entries(params)) {
3364
+ if (consumed.has(key) || value === undefined) continue
3365
+ if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
3366
+ throw new TypeError(`Host API query parameter ${key} must be a string, number, or boolean.`)
3367
+ }
3368
+ query.set(key, String(value))
3369
+ }
3370
+ const suffix = query.toString()
3371
+ return suffix ? `${pathname}?${suffix}` : pathname
3372
+ }
3373
+
3374
+ export function buildHostApiContractPath<Params extends AnySchema>(
3375
+ contract: { readonly path: string; readonly params: Params },
3376
+ input: unknown,
3377
+ ): string {
3378
+ return buildValidatedHostApiPath(contract, input)
3379
+ }
3380
+
3381
+ export function buildHostApiPath<Name extends HostApiContractName>(name: Name, input: HostApiParams<Name>): string {
3382
+ return buildValidatedHostApiPath(HostApiContracts[name], input)
3383
+ }