1use std::{borrow::Cow, collections::BTreeMap};
2
3use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7pub const EXTENSION_PROTOCOL_VERSION: &str = "0.0.6";
8pub const PLUGIN_PRODUCER_REGISTER_METHOD: &str = "plugin/producer/register";
9pub const PLUGIN_PRODUCER_SEND_METHOD: &str = "plugin/producer/send";
10pub const PLUGIN_PRODUCER_UNREGISTER_METHOD: &str = "plugin/producer/unregister";
11pub const PLUGIN_PRODUCER_DISCARD_METHOD: &str = "plugin/producer/discard";
12pub const PLUGIN_RECOVERY_START_METHOD: &str = "plugin/recovery/start";
13pub const PLUGIN_RECOVERY_COMPLETE_METHOD: &str = "plugin/recovery/complete";
14pub const PLUGIN_INITIALIZE_METHOD: &str = "plugin/initialize";
15pub const PLUGIN_PING_METHOD: &str = "plugin/ping";
16pub const PLUGIN_SHUTDOWN_METHOD: &str = "plugin/shutdown";
17pub const PLUGIN_TOOLS_CALL_METHOD: &str = "plugin/tools/call";
18pub const PLUGIN_EVENT_METHOD: &str = "plugin/event";
19pub const PLUGIN_BUS_EVENT_METHOD: &str = "plugin/bus_event";
20pub const PLUGIN_EMIT_METHOD: &str = "plugin/emit";
21pub const PLUGIN_EMIT_RESULT_METHOD: &str = "plugin/emit_result";
22pub const PLUGIN_INTERCEPT_TOOL_BEFORE_CALL_METHOD: &str = "plugin/intercept/tool_before_call";
23pub const PLUGIN_INTERCEPT_TOOL_AFTER_RESULT_METHOD: &str = "plugin/intercept/tool_after_result";
24pub const PLUGIN_INTERCEPT_AGENT_BEFORE_START_METHOD: &str = "plugin/intercept/agent_before_start";
25pub const PLUGIN_INTERCEPT_SESSION_BEFORE_COMPACT_METHOD: &str =
26 "plugin/intercept/session_before_compact";
27pub const PLUGIN_INTERCEPT_USER_BEFORE_INPUT_METHOD: &str = "plugin/intercept/user_before_input";
28pub const PLUGIN_INTERCEPT_MODEL_BEFORE_REQUEST_METHOD: &str =
29 "plugin/intercept/model_before_request";
30pub const PLUGIN_INTERCEPT_PROVIDER_BEFORE_HEADERS_METHOD: &str =
31 "plugin/intercept/provider_before_headers";
32pub const PLUGIN_INTERCEPT_PROVIDER_BEFORE_REQUEST_METHOD: &str =
33 "plugin/intercept/provider_before_request";
34pub const PLUGIN_INTERCEPT_PROVIDER_AFTER_RESPONSE_METHOD: &str =
35 "plugin/intercept/provider_after_response";
36pub const PLUGIN_INTERCEPT_MESSAGE_END_METHOD: &str = "plugin/intercept/message_end";
37pub const PLUGIN_INTERCEPT_MODEL_BEFORE_SELECT_METHOD: &str =
38 "plugin/intercept/model_before_select";
39pub const PLUGIN_INTERCEPT_SESSION_BEFORE_FORK_METHOD: &str =
40 "plugin/intercept/session_before_fork";
41pub const PLUGIN_INTERCEPT_SESSION_BEFORE_REVERT_METHOD: &str =
42 "plugin/intercept/session_before_revert";
43
44#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
45pub struct ExtensionProtocolVersion(());
46
47impl ExtensionProtocolVersion {
48 #[must_use]
49 pub const fn current() -> Self {
50 Self(())
51 }
52}
53
54impl Serialize for ExtensionProtocolVersion {
55 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56 where
57 S: serde::Serializer,
58 {
59 serializer.serialize_str(EXTENSION_PROTOCOL_VERSION)
60 }
61}
62
63impl<'de> Deserialize<'de> for ExtensionProtocolVersion {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: serde::Deserializer<'de>,
67 {
68 let found = String::deserialize(deserializer)?;
69 if found == EXTENSION_PROTOCOL_VERSION {
70 Ok(Self::current())
71 } else {
72 Err(serde::de::Error::custom(format!(
73 "extension protocol version must be exactly {EXTENSION_PROTOCOL_VERSION}; found {found}"
74 )))
75 }
76 }
77}
78
79impl JsonSchema for ExtensionProtocolVersion {
80 fn inline_schema() -> bool {
81 true
82 }
83
84 fn schema_name() -> Cow<'static, str> {
85 Cow::Borrowed("ExtensionProtocolVersion")
86 }
87
88 fn json_schema(_: &mut SchemaGenerator) -> Schema {
89 json_schema!({"type":"string","const":"0.0.6"})
90 }
91}
92
93#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
94#[serde(deny_unknown_fields)]
95pub struct ExtensionEngineCapabilities {
96 pub producer_messaging: bool,
97 pub ping: bool,
98 pub shutdown: bool,
99 pub tools: bool,
100 pub event_streaming: bool,
101 pub event_publishing: bool,
102 pub interception: bool,
103}
104
105#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
106#[serde(rename_all = "snake_case")]
107pub enum ExtensionInterceptionHook {
108 ToolBeforeCall,
109 ToolAfterResult,
110 AgentBeforeStart,
111 SessionBeforeCompact,
112 UserBeforeInput,
113 ModelBeforeRequest,
114 ProviderBeforeHeaders,
115 ProviderBeforeRequest,
116 ProviderAfterResponse,
117 MessageEnd,
118 ModelBeforeSelect,
119 SessionBeforeFork,
120 SessionBeforeRevert,
121}
122
123#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
124#[serde(deny_unknown_fields)]
125pub struct ExtensionPluginCapabilities {
126 pub producer_messaging: bool,
128 pub tools: bool,
129 pub resources: bool,
130 pub subscribe_events: bool,
131 pub subscribe_bus: bool,
132 pub publish_bus: bool,
133 pub publish_session_events: bool,
134 pub intercept: Vec<ExtensionInterceptionHook>,
135}
136
137#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
138#[serde(deny_unknown_fields)]
139pub struct ExtensionInitializeParams {
140 pub protocol_version: ExtensionProtocolVersion,
141 pub engine_version: String,
142 pub capabilities: ExtensionEngineCapabilities,
143}
144
145#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
146#[serde(deny_unknown_fields)]
147pub struct ExtensionToolDeclaration {
148 pub name: String,
149 pub description: String,
150 pub parameters: Value,
151 pub permission_name: String,
152 #[serde(default)]
153 pub output: crate::ToolOutputDeclaration,
154 #[serde(deserialize_with = "crate::deserialize_required_option")]
155 #[schemars(with = "crate::NullableSchema<String>", required)]
156 pub primary_resource_param: Option<String>,
157}
158
159#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
160#[serde(deny_unknown_fields)]
161pub struct ExtensionInitializeResult {
162 pub protocol_version: ExtensionProtocolVersion,
163 pub name: String,
164 pub version: String,
165 pub capabilities: ExtensionPluginCapabilities,
166 pub tools: Vec<ExtensionToolDeclaration>,
167}
168
169#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
170#[serde(deny_unknown_fields)]
171pub struct ExtensionPingParams {}
172
173#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
174#[serde(deny_unknown_fields)]
175pub struct ExtensionPingResult {}
176
177#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
178#[serde(deny_unknown_fields)]
179pub struct ExtensionShutdownParams {}
180
181#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
184#[serde(deny_unknown_fields)]
185pub struct ExtensionProducerRegisterParams {
186 pub session_id: crate::SessionId,
187}
188
189#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
190#[serde(deny_unknown_fields)]
191pub struct ExtensionProducerRegisterResult {
192 pub producer_id: crate::ProducerId,
193}
194
195#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
199#[serde(deny_unknown_fields)]
200pub struct ExtensionProducerSendParams {
201 pub session_id: crate::SessionId,
202 pub producer_id: crate::ProducerId,
203 pub mode: crate::ProducerDeliveryMode,
204 pub idempotency_key: crate::ProducerIdempotencyKey,
205 #[serde(deserialize_with = "deserialize_producer_description")]
206 pub description: crate::SafeDisplayText,
207 pub body: String,
208}
209
210fn deserialize_producer_description<'de, D>(
211 deserializer: D,
212) -> Result<crate::SafeDisplayText, D::Error>
213where
214 D: serde::Deserializer<'de>,
215{
216 let description = crate::SafeDisplayText::deserialize(deserializer)?;
217 if description.as_str().trim().is_empty() {
218 return Err(serde::de::Error::custom("description must not be blank"));
219 }
220 Ok(description)
221}
222
223#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
226#[serde(deny_unknown_fields)]
227pub struct ExtensionProducerSendResult {
228 pub message_id: crate::ProducerMessageId,
229}
230
231#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
232#[serde(deny_unknown_fields)]
233pub struct ExtensionProducerUnregisterParams {
234 pub session_id: crate::SessionId,
235 pub producer_id: crate::ProducerId,
236}
237
238#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
240#[serde(deny_unknown_fields)]
241pub struct ExtensionProducerUnregisterResult {}
242
243#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
253#[serde(deny_unknown_fields)]
254pub struct ExtensionProducerDiscardParams {
255 pub session_id: crate::SessionId,
256 pub message_id: crate::ProducerMessageId,
257}
258
259#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
261#[serde(deny_unknown_fields)]
262pub struct ExtensionProducerDiscardResult {}
263
264#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
268#[serde(deny_unknown_fields)]
269pub struct ExtensionRecoveryStartParams {}
270
271#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
272#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
273pub enum ExtensionRecoveryOutcome {
274 Ready,
275 Failed { message: crate::SafeErrorMessage },
276}
277
278#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
281#[serde(deny_unknown_fields)]
282pub struct ExtensionRecoveryCompleteParams {
283 pub outcome: ExtensionRecoveryOutcome,
284}
285
286#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
287#[serde(deny_unknown_fields)]
288pub struct ExtensionRecoveryCompleteResult {}
289
290#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
291#[serde(deny_unknown_fields)]
292pub struct ExtensionToolCallParams {
293 pub tool: String,
294 pub session_id: crate::SessionId,
295 pub context_id: String,
296 pub invocation_id: crate::ToolCallId,
297 pub arguments: Value,
298 #[serde(deserialize_with = "crate::deserialize_required_option")]
299 #[schemars(with = "crate::NullableSchema<String>", required)]
300 pub resource: Option<String>,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub cancellation_token: Option<String>,
303}
304
305#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
306#[serde(deny_unknown_fields)]
307pub struct ExtensionToolCallResult {
308 pub output: crate::ToolCompletionOutput,
309 pub display: String,
310 pub is_error: bool,
311}
312
313#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
314#[serde(deny_unknown_fields)]
315pub struct ExtensionEventParams {
316 pub session_id: crate::SessionId,
317 pub context_id: String,
318 #[schemars(range(min = 1))]
319 pub seq: u64,
320 pub event: crate::EventPayload,
321 pub timestamp: jiff::Timestamp,
322}
323
324#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
325#[serde(deny_unknown_fields)]
326pub struct ExtensionBusEventParams {
327 pub session_id: crate::SessionId,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub context_id: Option<String>,
330 pub plugin: String,
331 pub name: String,
332 pub payload: Value,
333}
334
335#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
336#[serde(deny_unknown_fields)]
337pub struct ExtensionEmitParams {
340 pub session_id: crate::SessionId,
341 pub context_id: String,
342 #[schemars(length(min = 1, max = 128))]
343 pub name: String,
344 pub payload: Value,
345}
346
347#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
348#[serde(rename_all = "snake_case")]
349pub enum ExtensionEmitStatus {
350 Published,
351 Dropped,
352 Rejected,
353}
354
355#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
356#[serde(deny_unknown_fields)]
357pub struct ExtensionEmitResultParams {
358 pub name: String,
359 pub bus: ExtensionEmitStatus,
360 pub durable: ExtensionEmitStatus,
361 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub reason: Option<String>,
363}
364
365#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
366#[serde(deny_unknown_fields)]
367pub struct ExtensionToolBeforeCallParams {
368 pub session_id: crate::SessionId,
369 pub context_id: String,
370 pub tool: String,
371 pub arguments: Value,
372 pub permission_name: String,
373 #[serde(deserialize_with = "crate::deserialize_required_option")]
374 #[schemars(with = "crate::NullableSchema<String>", required)]
375 pub resource: Option<String>,
376}
377
378#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
379#[serde(rename_all = "snake_case")]
380pub enum ExtensionToolBeforeCallAction {
381 Allow,
382 Block,
383}
384
385#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
386#[serde(deny_unknown_fields)]
387pub struct ExtensionToolBeforeCallResult {
388 pub action: ExtensionToolBeforeCallAction,
389 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub reason: Option<String>,
391 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub modified_arguments: Option<Value>,
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub message_to_model: Option<String>,
395}
396
397#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
398#[serde(deny_unknown_fields)]
399pub struct ExtensionToolAfterResultParams {
400 pub session_id: crate::SessionId,
401 pub context_id: String,
402 pub tool: String,
403 pub arguments: Value,
404 pub result_content: String,
405 pub is_error: bool,
406}
407
408#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
409#[serde(rename_all = "snake_case")]
410pub enum ExtensionToolAfterResultAction {
411 Keep,
412 Replace,
413}
414
415#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
416#[serde(deny_unknown_fields)]
417pub struct ExtensionToolAfterResultResult {
418 pub action: ExtensionToolAfterResultAction,
419 #[serde(default, skip_serializing_if = "Option::is_none")]
420 pub replacement_content: Option<String>,
421 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub note: Option<String>,
423}
424
425#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
426#[serde(deny_unknown_fields)]
427pub struct ExtensionAgentBeforeStartParams {
428 pub session_id: crate::SessionId,
429 pub context_id: String,
430 pub agent_path: String,
431 pub prompt_context: Value,
432}
433
434#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
435#[serde(deny_unknown_fields)]
436pub struct ExtensionAgentBeforeStartResult {
437 #[serde(default, skip_serializing_if = "Option::is_none")]
438 pub addendum: Option<String>,
439 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub append_to_system_prompt: Option<String>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub replace_system_prompt: Option<String>,
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub inject_message: Option<ExtensionInjectedMessage>,
445}
446
447#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
448#[serde(rename_all = "snake_case")]
449pub enum ExtensionMessageRole {
450 System,
451 User,
452 Assistant,
453 Tool,
454}
455
456#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
457#[serde(deny_unknown_fields)]
458pub struct ExtensionInjectedMessage {
459 pub role: ExtensionMessageRole,
460 pub content: String,
461}
462
463#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
464#[serde(deny_unknown_fields)]
465pub struct ExtensionSessionBeforeCompactParams {
466 pub session_id: crate::SessionId,
467 pub context_id: String,
468 pub checkpoint_id: String,
469 pub additions: Vec<String>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub instructions: Option<String>,
472}
473
474#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
475#[serde(deny_unknown_fields)]
476pub struct ExtensionSessionBeforeCompactResult {
477 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub addendum: Option<String>,
479 #[serde(default)]
480 pub cancel: bool,
481 #[serde(default, skip_serializing_if = "Option::is_none")]
482 pub reason: Option<String>,
483 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub instructions_override: Option<String>,
485}
486
487#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
488#[serde(deny_unknown_fields)]
489pub struct ExtensionUserBeforeInputParams {
490 pub session_id: crate::SessionId,
491 pub context_id: String,
492 pub text: String,
493}
494
495#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
496#[serde(rename_all = "snake_case")]
497pub enum ExtensionUserBeforeInputAction {
498 Allow,
499 Transform,
500 Handled,
501}
502
503#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
504#[serde(deny_unknown_fields)]
505pub struct ExtensionUserBeforeInputResult {
506 pub action: ExtensionUserBeforeInputAction,
507 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub new_text: Option<String>,
509 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub reason: Option<String>,
511}
512
513#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
514#[serde(deny_unknown_fields)]
515pub struct ExtensionModelMessage {
516 pub role: ExtensionMessageRole,
517 pub content: Value,
518}
519
520#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)]
521#[serde(deny_unknown_fields)]
522pub struct ExtensionModelParamsAdjustments {
523 #[serde(default, skip_serializing_if = "Option::is_none")]
524 pub max_tokens: Option<u64>,
525 #[serde(default, skip_serializing_if = "Option::is_none")]
526 pub temperature: Option<f64>,
527 #[serde(default, skip_serializing_if = "Option::is_none")]
528 pub top_p: Option<f64>,
529}
530
531#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
532#[serde(deny_unknown_fields)]
533pub struct ExtensionModelBeforeRequestParams {
534 pub session_id: crate::SessionId,
535 pub context_id: String,
536 pub attempt_id: crate::AttemptId,
537 pub messages: Vec<ExtensionModelMessage>,
538 pub model: crate::ResolvedModelRef,
539 pub params: ExtensionModelParamsAdjustments,
540}
541
542#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
543#[serde(rename_all = "snake_case")]
544pub enum ExtensionModelBeforeRequestAction {
545 Keep,
546 Replace,
547}
548
549#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
550#[serde(deny_unknown_fields)]
551pub struct ExtensionModelBeforeRequestResult {
552 pub action: ExtensionModelBeforeRequestAction,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 pub messages: Option<Vec<ExtensionModelMessage>>,
555 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub params_adjustments: Option<ExtensionModelParamsAdjustments>,
557}
558
559#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
560#[serde(deny_unknown_fields)]
561pub struct ExtensionProviderBeforeHeadersParams {
562 pub session_id: crate::SessionId,
563 pub context_id: String,
564 pub attempt_id: crate::AttemptId,
565 pub headers: BTreeMap<String, String>,
566}
567
568#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
569#[serde(deny_unknown_fields)]
570pub struct ExtensionProviderBeforeHeadersResult {
571 #[serde(default)]
572 pub set: BTreeMap<String, String>,
573 #[serde(default)]
574 pub delete: Vec<String>,
575}
576
577#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
578#[serde(deny_unknown_fields)]
579pub struct ExtensionProviderBeforeRequestParams {
580 pub session_id: crate::SessionId,
581 pub context_id: String,
582 pub attempt_id: crate::AttemptId,
583 pub payload: Value,
584}
585
586#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
587#[serde(rename_all = "snake_case")]
588pub enum ExtensionProviderBeforeRequestAction {
589 Keep,
590 Replace,
591}
592
593#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
594#[serde(deny_unknown_fields)]
595pub struct ExtensionProviderBeforeRequestResult {
596 pub action: ExtensionProviderBeforeRequestAction,
597 #[serde(default, skip_serializing_if = "Option::is_none")]
598 pub payload: Option<Value>,
599}
600
601#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
602#[serde(deny_unknown_fields)]
603pub struct ExtensionProviderAfterResponseParams {
604 pub session_id: crate::SessionId,
605 pub context_id: String,
606 pub attempt_id: crate::AttemptId,
607 pub status: u16,
608 pub headers: BTreeMap<String, String>,
609}
610
611#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
612#[serde(deny_unknown_fields)]
613pub struct ExtensionProviderAfterResponseResult {}
614
615#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
616#[serde(deny_unknown_fields)]
617pub struct ExtensionMessageEndParams {
618 pub session_id: crate::SessionId,
619 pub context_id: String,
620 pub attempt_id: crate::AttemptId,
621 pub role: ExtensionMessageRole,
622 pub content: Vec<crate::PersistedAssistantPart>,
623}
624
625#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
626#[serde(rename_all = "snake_case")]
627pub enum ExtensionMessageEndAction {
628 Keep,
629 Replace,
630}
631
632#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
633#[serde(deny_unknown_fields)]
634pub struct ExtensionMessageEndResult {
635 pub action: ExtensionMessageEndAction,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
637 pub content: Option<Vec<crate::PersistedAssistantPart>>,
638}
639
640#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
641#[serde(rename_all = "snake_case")]
642pub enum ExtensionModelSelectSource {
643 User,
644 Config,
645 FallbackRestore,
646}
647
648#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
649#[serde(deny_unknown_fields)]
650pub struct ExtensionModelBeforeSelectParams {
651 pub session_id: crate::SessionId,
652 pub context_id: String,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub from: Option<cookie_agent_identity::ModelSelection>,
655 pub to: cookie_agent_identity::ModelSelection,
656 pub source: ExtensionModelSelectSource,
657}
658
659#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
660#[serde(rename_all = "snake_case")]
661pub enum ExtensionAllowBlockAction {
662 Allow,
663 Block,
664}
665
666#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
667#[serde(deny_unknown_fields)]
668pub struct ExtensionAllowBlockResult {
669 pub action: ExtensionAllowBlockAction,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
671 pub reason: Option<String>,
672}
673
674#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
675#[serde(deny_unknown_fields)]
676pub struct ExtensionSessionBeforeForkParams {
677 pub session_id: crate::SessionId,
678 pub context_id: String,
679 pub through_seq: u64,
680}
681
682pub type ExtensionSessionBeforeForkResult = ExtensionAllowBlockResult;
683
684#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
685#[serde(deny_unknown_fields)]
686pub struct ExtensionSessionBeforeRevertParams {
687 pub session_id: crate::SessionId,
688 pub context_id: String,
689 pub through_seq: u64,
690 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub instructions: Option<String>,
692}
693
694#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
695#[serde(rename_all = "snake_case")]
696pub enum ExtensionSessionBeforeRevertAction {
697 Allow,
698 Block,
699 Override,
700}
701
702#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
703#[serde(deny_unknown_fields)]
704pub struct ExtensionSessionBeforeRevertResult {
705 pub action: ExtensionSessionBeforeRevertAction,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub reason: Option<String>,
708 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub instructions_override: Option<String>,
710}
711
712#[must_use]
713pub fn extension_initialize_request(engine_version: impl Into<String>) -> crate::Request {
714 let params = ExtensionInitializeParams {
715 protocol_version: ExtensionProtocolVersion::current(),
716 engine_version: engine_version.into(),
717 capabilities: ExtensionEngineCapabilities {
718 producer_messaging: true,
719 ping: true,
720 shutdown: true,
721 tools: true,
722 event_streaming: true,
723 event_publishing: true,
724 interception: true,
725 },
726 };
727 crate::Request::new(
728 crate::JsonRpcId::Number(1),
729 PLUGIN_INITIALIZE_METHOD,
730 Some(serde_json::to_value(params).expect("extension initialize params serialize")),
731 )
732}
733
734#[must_use]
735pub fn extension_recovery_start_notification() -> crate::Notification {
736 crate::Notification::new(
737 PLUGIN_RECOVERY_START_METHOD,
738 Some(
739 serde_json::to_value(ExtensionRecoveryStartParams {})
740 .expect("recovery start params serialize"),
741 ),
742 )
743}
744
745#[must_use]
746pub fn extension_shutdown_notification() -> crate::Notification {
747 crate::Notification::new(PLUGIN_SHUTDOWN_METHOD, Some(serde_json::json!({})))
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
755 fn producer_discard_is_strict_and_registration_independent() {
756 let params = ExtensionProducerDiscardParams {
757 session_id: crate::SessionId::new_v7(),
758 message_id: crate::ProducerMessageId::new_v7(),
759 };
760 let wire = serde_json::to_value(params).unwrap();
761 assert_eq!(
762 serde_json::from_value::<ExtensionProducerDiscardParams>(wire.clone()).unwrap(),
763 params
764 );
765 assert_eq!(
766 wire,
767 serde_json::json!({"session_id": params.session_id, "message_id": params.message_id})
768 );
769 for field in ["producer_id", "plugin", "connection_epoch"] {
770 let mut forged = wire.clone();
771 forged[field] = serde_json::json!("foreign");
772 assert!(serde_json::from_value::<ExtensionProducerDiscardParams>(forged).is_err());
773 }
774 for field in ["session_id", "message_id"] {
775 let mut missing = wire.clone();
776 missing.as_object_mut().unwrap().remove(field);
777 assert!(serde_json::from_value::<ExtensionProducerDiscardParams>(missing).is_err());
778 let mut invalid = wire.clone();
779 invalid[field] = serde_json::json!("not-a-uuid");
780 assert!(serde_json::from_value::<ExtensionProducerDiscardParams>(invalid).is_err());
781 }
782 assert_eq!(PLUGIN_PRODUCER_DISCARD_METHOD, "plugin/producer/discard");
783 assert_eq!(
784 serde_json::to_value(ExtensionProducerDiscardResult {}).unwrap(),
785 serde_json::json!({})
786 );
787 assert!(
788 serde_json::from_value::<ExtensionProducerDiscardResult>(serde_json::json!({})).is_ok()
789 );
790 assert!(
791 serde_json::from_value::<ExtensionProducerDiscardResult>(
792 serde_json::json!({"message_id": params.message_id})
793 )
794 .is_err()
795 );
796 }
797
798 #[test]
799 fn protocol_version_rejection_reports_found_value() {
800 let error = serde_json::from_str::<ExtensionProtocolVersion>("\"0.0.1\"")
801 .expect_err("version mismatch");
802 assert!(error.to_string().contains("0.0.1"));
803 }
804}