context-compiler-example-integrations 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. context_compiler_example_integrations/README.md +71 -0
  2. context_compiler_example_integrations/__init__.py +1 -0
  3. context_compiler_example_integrations/examples/__init__.py +12 -0
  4. context_compiler_example_integrations/examples/_shared/__init__.py +1 -0
  5. context_compiler_example_integrations/examples/_shared/litellm_request.py +61 -0
  6. context_compiler_example_integrations/examples/_shared/provider_mode.py +91 -0
  7. context_compiler_example_integrations/examples/checkpoint_continuation/README.md +77 -0
  8. context_compiler_example_integrations/examples/checkpoint_continuation/example.py +169 -0
  9. context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/README.md +85 -0
  10. context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/app.py +205 -0
  11. context_compiler_example_integrations/examples/execution_authorization/README.md +31 -0
  12. context_compiler_example_integrations/examples/execution_authorization/expense_approval/README.md +77 -0
  13. context_compiler_example_integrations/examples/execution_authorization/expense_approval/__init__.py +1 -0
  14. context_compiler_example_integrations/examples/execution_authorization/expense_approval/example.py +168 -0
  15. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/README.md +97 -0
  16. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/__init__.py +1 -0
  17. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/app.py +312 -0
  18. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/model_approval.py +98 -0
  19. context_compiler_example_integrations/examples/gateway_middleware/README.md +49 -0
  20. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/README.md +40 -0
  21. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/__init__.py +1 -0
  22. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/example.py +220 -0
  23. context_compiler_example_integrations/examples/prompt_construction/README.md +27 -0
  24. context_compiler_example_integrations/examples/prompt_construction/litellm/README.md +197 -0
  25. context_compiler_example_integrations/examples/prompt_construction/litellm/basic.py +392 -0
  26. context_compiler_example_integrations/examples/prompt_construction/litellm/confirmation_helper.py +91 -0
  27. context_compiler_example_integrations/examples/prompt_construction/litellm/with_directive_drafter.py +524 -0
  28. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/README.md +92 -0
  29. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/__init__.py +1 -0
  30. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/example.py +205 -0
  31. context_compiler_example_integrations/examples/retrieval_filtering/README.md +65 -0
  32. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/README.md +120 -0
  33. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/__init__.py +1 -0
  34. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +320 -0
  35. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/README.md +123 -0
  36. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/__init__.py +1 -0
  37. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/example.py +300 -0
  38. context_compiler_example_integrations/examples/schema_selection/README.md +41 -0
  39. context_compiler_example_integrations/examples/schema_selection/litellm_response_format/response_format.py +218 -0
  40. context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/README.md +60 -0
  41. context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/example.py +171 -0
  42. context_compiler_example_integrations/examples/schema_selection/refund_intake/README.md +95 -0
  43. context_compiler_example_integrations/examples/schema_selection/refund_intake/__init__.py +1 -0
  44. context_compiler_example_integrations/examples/schema_selection/refund_intake/example.py +180 -0
  45. context_compiler_example_integrations/examples/tool_gating/README.md +55 -0
  46. context_compiler_example_integrations/examples/tool_gating/calendar_admin/README.md +34 -0
  47. context_compiler_example_integrations/examples/tool_gating/calendar_admin/__init__.py +1 -0
  48. context_compiler_example_integrations/examples/tool_gating/calendar_admin/example.py +195 -0
  49. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/README.md +93 -0
  50. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/__init__.py +1 -0
  51. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/example.py +250 -0
  52. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/live_model.py +369 -0
  53. context_compiler_example_integrations/reference_integrations/__init__.py +12 -0
  54. context_compiler_example_integrations/reference_integrations/litellm_proxy/README.md +251 -0
  55. context_compiler_example_integrations/reference_integrations/litellm_proxy/_checkpoint_support.py +146 -0
  56. context_compiler_example_integrations/reference_integrations/litellm_proxy/config.example.yaml +23 -0
  57. context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook.py +163 -0
  58. context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +289 -0
  59. context_compiler_example_integrations/reference_integrations/openwebui_pipe/README.md +233 -0
  60. context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe.py +750 -0
  61. context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +1045 -0
  62. context_compiler_example_integrations-0.1.0.dist-info/METADATA +110 -0
  63. context_compiler_example_integrations-0.1.0.dist-info/RECORD +65 -0
  64. context_compiler_example_integrations-0.1.0.dist-info/WHEEL +4 -0
  65. context_compiler_example_integrations-0.1.0.dist-info/licenses/LICENSE +173 -0
@@ -0,0 +1,392 @@
1
+ """Minimal LiteLLM integration with Context Compiler.
2
+
3
+ Flow:
4
+ 1. Call engine.step(user_input)
5
+ 2. clarify -> return prompt_to_user (no model call)
6
+ 3. update -> return deterministic acknowledgment text (no model call)
7
+ 4. passthrough -> call LiteLLM with compiled state + user input
8
+
9
+ Intended host usage:
10
+ - collect user input
11
+ - call handle_turn(user_input, engine)
12
+ - display returned assistant text
13
+ """
14
+
15
+ import logging
16
+ import re
17
+ from collections.abc import Callable, Mapping, Sequence
18
+ from importlib import import_module
19
+ from typing import TypedDict, cast
20
+
21
+ from context_compiler import (
22
+ DECISION_CLARIFY,
23
+ DECISION_PASSTHROUGH,
24
+ DECISION_UPDATE,
25
+ POLICY_PROHIBIT,
26
+ POLICY_USE,
27
+ State,
28
+ get_clarify_prompt,
29
+ get_decision_state,
30
+ get_policy_items,
31
+ get_premise_value,
32
+ is_clarify,
33
+ is_passthrough,
34
+ is_update,
35
+ state_diff,
36
+ )
37
+ from context_compiler.engine import Engine
38
+
39
+ try:
40
+ from .confirmation_helper import (
41
+ is_confirmation_text,
42
+ summarize_confirmation_update_from_checkpoint,
43
+ )
44
+ except ImportError:
45
+ from confirmation_helper import (
46
+ is_confirmation_text,
47
+ summarize_confirmation_update_from_checkpoint,
48
+ )
49
+
50
+ from context_compiler_example_integrations.examples._shared.provider_mode import (
51
+ print_startup_config,
52
+ resolve_provider_config,
53
+ )
54
+
55
+ logger = logging.getLogger(__name__)
56
+ # Example-only in-memory checkpoint store.
57
+ # This keeps continuation state only for the current process lifetime.
58
+ # Real deployments should persist checkpoints externally (DB/Redis/etc.),
59
+ # or restart continuity for pending flows will be lost.
60
+ _CHECKPOINTS_BY_SESSION_KEY: dict[str, str] = {}
61
+ _RESTORED_ENGINE_BY_SESSION_KEY: dict[str, int] = {}
62
+ SHOW_CONTEXT_COMPILER_TRACE = False
63
+
64
+
65
+ class _LiteLLMCallKwargs(TypedDict, total=False):
66
+ model: str
67
+ messages: list[dict[str, str]]
68
+ api_key: str
69
+ temperature: float
70
+ api_base: str
71
+
72
+
73
+ def _extract_response_content(response: object) -> str | None:
74
+ if isinstance(response, Mapping):
75
+ choices = response.get("choices")
76
+ if isinstance(choices, Sequence) and choices:
77
+ first = choices[0]
78
+ if isinstance(first, Mapping):
79
+ message = first.get("message")
80
+ if isinstance(message, Mapping):
81
+ content = message.get("content")
82
+ if isinstance(content, str):
83
+ return content
84
+
85
+ choices_attr = getattr(response, "choices", None)
86
+ if isinstance(choices_attr, Sequence) and choices_attr:
87
+ first = choices_attr[0]
88
+ message_attr = getattr(first, "message", None)
89
+ content_attr = getattr(message_attr, "content", None)
90
+ if isinstance(content_attr, str):
91
+ return content_attr
92
+
93
+ return None
94
+
95
+
96
+ def _render_state_lines(state: object) -> list[str]:
97
+ if not isinstance(state, dict):
98
+ return ["- unavailable"]
99
+ typed_state = cast(State, state)
100
+
101
+ premise = get_premise_value(typed_state)
102
+ use_items = sorted(get_policy_items(typed_state, POLICY_USE))
103
+ prohibit_items = sorted(get_policy_items(typed_state, POLICY_PROHIBIT))
104
+
105
+ lines = [f"- premise: {premise if premise is not None else '(none)'}"]
106
+ lines.append(f"- use: {', '.join(use_items) if use_items else '(none)'}")
107
+ lines.append(
108
+ f"- prohibit: {', '.join(prohibit_items) if prohibit_items else '(none)'}"
109
+ )
110
+ return lines
111
+
112
+
113
+ def _build_trace_text(
114
+ *,
115
+ original_input: str,
116
+ compiler_input: str,
117
+ decision: object,
118
+ state_before: object,
119
+ state_after: object,
120
+ llm_called: bool,
121
+ ) -> str:
122
+ kind = decision.get("kind", "unknown") if isinstance(decision, dict) else "unknown"
123
+ lines = [
124
+ "Context Compiler trace",
125
+ f"- original_input: {original_input}",
126
+ f"- compiler_input: {compiler_input}",
127
+ f"- decision: {kind}",
128
+ f"- llm_called: {'yes' if llm_called else 'no'}",
129
+ ]
130
+ if isinstance(state_before, dict) and isinstance(state_after, dict):
131
+ diff = state_diff(cast(State, state_before), cast(State, state_after))
132
+ lines.append(f"- state_changed: {'yes' if diff['changed'] else 'no'}")
133
+ lines.append("state_before:")
134
+ lines.extend(_render_state_lines(state_before))
135
+ lines.append("state_after:")
136
+ lines.extend(_render_state_lines(state_after))
137
+ return "\n".join(lines)
138
+
139
+
140
+ def _render_compiled_state_contract(compiled_state: State) -> str:
141
+ premise = get_premise_value(compiled_state)
142
+ use_items = sorted(get_policy_items(compiled_state, POLICY_USE))
143
+ prohibit_items = sorted(get_policy_items(compiled_state, POLICY_PROHIBIT))
144
+
145
+ lines: list[str] = ["The following constraints are authoritative."]
146
+ if premise:
147
+ lines.append(f"Current premise: {premise}.")
148
+ if use_items:
149
+ lines.append("Items marked use: " + ", ".join(use_items) + ".")
150
+ if prohibit_items:
151
+ lines.append("Items marked prohibit: " + ", ".join(prohibit_items) + ".")
152
+ lines.append("If user text conflicts with constraints, follow constraints exactly.")
153
+
154
+ return "Host policy contract:\n" + "\n".join(f"- {line}" for line in lines)
155
+
156
+
157
+ def _build_messages(user_input: str, compiled_state: State) -> list[dict[str, str]]:
158
+ return [
159
+ {
160
+ "role": "system",
161
+ "content": "You are a helpful assistant.\n"
162
+ + _render_compiled_state_contract(compiled_state),
163
+ },
164
+ {"role": "user", "content": user_input},
165
+ ]
166
+
167
+
168
+ def _call_litellm(messages: list[dict[str, str]]) -> str:
169
+ try:
170
+ litellm_module = import_module("litellm")
171
+ except ModuleNotFoundError as exc:
172
+ raise RuntimeError(
173
+ "litellm is required. Install with: pip install litellm"
174
+ ) from exc
175
+ completion_fn = cast(Callable[..., object], litellm_module.completion)
176
+
177
+ config = resolve_provider_config(default_model="openai/gpt-4o-mini")
178
+ print_startup_config(config, logger=logger)
179
+
180
+ kwargs: _LiteLLMCallKwargs = {
181
+ "model": config.model,
182
+ "messages": messages,
183
+ "temperature": 0,
184
+ "api_base": config.base_url,
185
+ }
186
+ if config.api_key:
187
+ kwargs["api_key"] = config.api_key
188
+
189
+ response = completion_fn(**kwargs)
190
+ content = _extract_response_content(response)
191
+ if content is None:
192
+ raise RuntimeError("LiteLLM response missing choices[0].message.content")
193
+ return content
194
+
195
+
196
+ def _restore_session_checkpoint_if_needed(
197
+ engine: Engine, session_key: str | None
198
+ ) -> None:
199
+ if session_key is None:
200
+ return
201
+ engine_id = id(engine)
202
+ if _RESTORED_ENGINE_BY_SESSION_KEY.get(session_key) == engine_id:
203
+ return
204
+
205
+ checkpoint = _CHECKPOINTS_BY_SESSION_KEY.get(session_key)
206
+ if checkpoint is not None:
207
+ engine.import_checkpoint_json(checkpoint)
208
+ _RESTORED_ENGINE_BY_SESSION_KEY[session_key] = engine_id
209
+
210
+
211
+ def _persist_session_checkpoint_if_needed(
212
+ engine: Engine, kind: str, session_key: str | None
213
+ ) -> None:
214
+ if session_key is None:
215
+ return
216
+ if kind not in {DECISION_UPDATE, DECISION_CLARIFY}:
217
+ return
218
+ _CHECKPOINTS_BY_SESSION_KEY[session_key] = engine.export_checkpoint_json()
219
+
220
+
221
+ def _render_item_label(value: str) -> str:
222
+ return re.sub(r"\s+", " ", value).strip().lower()
223
+
224
+
225
+ def _near_miss_directive_clarify(value: str) -> str | None:
226
+ normalized = re.sub(r"\s+", " ", value.strip())
227
+ lower = normalized.lower()
228
+
229
+ if lower in {"reset premise", "reset premises", "clear premises"}:
230
+ return "Unknown directive.\nUse 'clear premise' or 'reset policies'."
231
+ if lower.startswith("set premise to "):
232
+ return "Invalid premise syntax.\nUse 'set premise <value>'."
233
+ if lower.startswith("change premise ") and not lower.startswith(
234
+ "change premise to "
235
+ ):
236
+ return "Invalid premise syntax.\nUse 'change premise to <value>'."
237
+ return None
238
+
239
+
240
+ def _summarize_confirmation_update(user_input: str, checkpoint: object) -> str:
241
+ summarize_fn: Callable[[str, object], str] = (
242
+ summarize_confirmation_update_from_checkpoint
243
+ )
244
+ return summarize_fn(user_input, checkpoint)
245
+
246
+
247
+ def _summarize_update_from_input(user_input: str) -> str:
248
+ normalized = re.sub(r"\s+", " ", user_input.strip())
249
+ lower = normalized.lower()
250
+
251
+ if lower == "clear state":
252
+ return "State cleared."
253
+ if lower == "clear premise":
254
+ return "Premise cleared."
255
+ if lower == "reset policies":
256
+ return "Policies reset."
257
+
258
+ replacement_match = re.match(
259
+ r"^use\s+(.+?)\s+instead\s+of\s+(.+)$", normalized, flags=re.IGNORECASE
260
+ )
261
+ if replacement_match is not None:
262
+ item = _render_item_label(replacement_match.group(1).rstrip(" .!?"))
263
+ if item:
264
+ return f"State updated: Use {item}."
265
+
266
+ use_match = re.match(r"^use\s+(.+)$", normalized, flags=re.IGNORECASE)
267
+ if use_match is not None:
268
+ item = _render_item_label(use_match.group(1).rstrip(" .!?"))
269
+ if item:
270
+ return f"State updated: Use {item}."
271
+
272
+ prohibit_match = re.match(r"^prohibit\s+(.+)$", normalized, flags=re.IGNORECASE)
273
+ if prohibit_match is not None:
274
+ item = _render_item_label(prohibit_match.group(1).rstrip(" .!?"))
275
+ if item:
276
+ return f"State updated: Prohibit {item}."
277
+
278
+ remove_policy_match = re.match(
279
+ r"^remove\s+policy\s+(.+)$", normalized, flags=re.IGNORECASE
280
+ )
281
+ if remove_policy_match is not None:
282
+ item = _render_item_label(remove_policy_match.group(1).rstrip(" .!?"))
283
+ if item:
284
+ return f"State updated: Removed policy {item}."
285
+
286
+ return "State updated."
287
+
288
+
289
+ def _append_trace(
290
+ response_text: str,
291
+ *,
292
+ original_input: str,
293
+ compiler_input: str,
294
+ decision: object,
295
+ state_before: object,
296
+ state_after: object,
297
+ llm_called: bool,
298
+ ) -> str:
299
+ if not SHOW_CONTEXT_COMPILER_TRACE:
300
+ return response_text
301
+ trace_text = _build_trace_text(
302
+ original_input=original_input,
303
+ compiler_input=compiler_input,
304
+ decision=decision,
305
+ state_before=state_before,
306
+ state_after=state_after,
307
+ llm_called=llm_called,
308
+ )
309
+ return f"{response_text}\n\n{trace_text}"
310
+
311
+
312
+ def handle_turn(
313
+ user_input: str, engine: Engine, *, session_key: str | None = None
314
+ ) -> str:
315
+ _restore_session_checkpoint_if_needed(engine, session_key)
316
+ state_before = engine.state
317
+ has_pending_before = engine.has_pending_clarification()
318
+ checkpoint_before = engine.export_checkpoint() if has_pending_before else None
319
+ logger.debug("litellm_basic: engine_input=%s", f"user_input len={len(user_input)}")
320
+ decision = engine.step(user_input)
321
+ if is_clarify(decision):
322
+ kind = DECISION_CLARIFY
323
+ elif is_update(decision):
324
+ kind = DECISION_UPDATE
325
+ else:
326
+ kind = DECISION_PASSTHROUGH
327
+ logger.debug("litellm_basic: decision=%s", kind)
328
+ near_miss_prompt = _near_miss_directive_clarify(user_input)
329
+
330
+ if is_clarify(decision):
331
+ _persist_session_checkpoint_if_needed(engine, kind, session_key)
332
+ response_text = near_miss_prompt or get_clarify_prompt(decision) or ""
333
+ return _append_trace(
334
+ response_text,
335
+ original_input=user_input,
336
+ compiler_input=user_input,
337
+ decision=decision,
338
+ state_before=state_before,
339
+ state_after=engine.state,
340
+ llm_called=False,
341
+ )
342
+ if near_miss_prompt is not None and is_passthrough(decision):
343
+ return _append_trace(
344
+ near_miss_prompt,
345
+ original_input=user_input,
346
+ compiler_input=user_input,
347
+ decision={"kind": DECISION_CLARIFY, "prompt_to_user": near_miss_prompt},
348
+ state_before=state_before,
349
+ state_after=engine.state,
350
+ llm_called=False,
351
+ )
352
+ _persist_session_checkpoint_if_needed(engine, kind, session_key)
353
+ if (
354
+ is_update(decision)
355
+ and is_confirmation_text(user_input)
356
+ and checkpoint_before is not None
357
+ ):
358
+ response_text = _summarize_confirmation_update(user_input, checkpoint_before)
359
+ return _append_trace(
360
+ response_text,
361
+ original_input=user_input,
362
+ compiler_input=user_input,
363
+ decision=decision,
364
+ state_before=state_before,
365
+ state_after=engine.state,
366
+ llm_called=False,
367
+ )
368
+ if is_update(decision):
369
+ response_text = _summarize_update_from_input(user_input)
370
+ return _append_trace(
371
+ response_text,
372
+ original_input=user_input,
373
+ compiler_input=user_input,
374
+ decision=decision,
375
+ state_before=state_before,
376
+ state_after=engine.state,
377
+ llm_called=False,
378
+ )
379
+
380
+ decision_state = get_decision_state(decision)
381
+ compiled_state = decision_state if decision_state is not None else engine.state
382
+ messages = _build_messages(user_input, compiled_state)
383
+ response_text = _call_litellm(messages)
384
+ return _append_trace(
385
+ response_text,
386
+ original_input=user_input,
387
+ compiler_input=user_input,
388
+ decision=decision,
389
+ state_before=state_before,
390
+ state_after=compiled_state,
391
+ llm_called=True,
392
+ )
@@ -0,0 +1,91 @@
1
+ """Local confirmation helpers for the LiteLLM prompt-construction examples.
2
+
3
+ This keeps example behavior deterministic within this repository instead of
4
+ depending on the separately versioned host_support package.
5
+ """
6
+
7
+ import re
8
+
9
+ _TRAILING_CONFIRM_PUNCT_RE = re.compile(r"[.,!?]+$")
10
+
11
+ _AFFIRMATIVE_CONFIRMATION_TOKENS = frozenset(
12
+ {"yes", "yes please", "yep", "yeah", "sure", "ok", "okay"}
13
+ )
14
+ _NEGATIVE_CONFIRMATION_TOKENS = frozenset({"no", "nope", "no thanks"})
15
+
16
+ CONFIRMATION_TOKENS: frozenset[str] = (
17
+ _AFFIRMATIVE_CONFIRMATION_TOKENS | _NEGATIVE_CONFIRMATION_TOKENS
18
+ )
19
+
20
+
21
+ def _render_item_label(value: str) -> str:
22
+ return re.sub(r"\s+", " ", value).strip()
23
+
24
+
25
+ def _normalize_confirmation_text(value: str) -> str:
26
+ normalized = value.strip().lower()
27
+ normalized = re.sub(r"\s+", " ", normalized)
28
+ normalized = _TRAILING_CONFIRM_PUNCT_RE.sub("", normalized).strip()
29
+ return re.sub(r"\s+", " ", normalized)
30
+
31
+
32
+ def is_confirmation_text(value: str) -> bool:
33
+ return _normalize_confirmation_text(value) in CONFIRMATION_TOKENS
34
+
35
+
36
+ def _summarize_pending_confirmation_update(pending: object) -> str:
37
+ if not isinstance(pending, dict):
38
+ return "State updated."
39
+
40
+ replacement = pending.get("replacement")
41
+ if not isinstance(replacement, dict):
42
+ return "State updated."
43
+
44
+ kind = replacement.get("kind")
45
+ new_item = replacement.get("new_item")
46
+ old_item = replacement.get("old_item")
47
+
48
+ if kind == "use_only" and isinstance(new_item, str):
49
+ new_label = _render_item_label(new_item)
50
+ if new_label:
51
+ return f"State updated: Use {new_label}."
52
+ return "State updated."
53
+
54
+ if (
55
+ kind == "replace_use"
56
+ and isinstance(new_item, str)
57
+ and isinstance(old_item, str)
58
+ ):
59
+ new_label = _render_item_label(new_item)
60
+ old_label = _render_item_label(old_item)
61
+ if not new_label or not old_label:
62
+ return "State updated."
63
+
64
+ prompt = pending.get("prompt_to_user")
65
+ prohibited_old_prompt = (
66
+ f'"{old_item}" is currently prohibited. '
67
+ f'Did you mean to remove it and use "{new_item}" instead?'
68
+ )
69
+ if prompt == prohibited_old_prompt:
70
+ return (
71
+ f"State updated: Removed prohibition on {old_label}; use {new_label}."
72
+ )
73
+ return f"State updated: Replaced {old_label} with {new_label}."
74
+
75
+ return "State updated."
76
+
77
+
78
+ def summarize_confirmation_update(user_input: str, pending: object) -> str:
79
+ normalized = _normalize_confirmation_text(user_input)
80
+ if normalized in _NEGATIVE_CONFIRMATION_TOKENS:
81
+ return "State unchanged."
82
+ if normalized not in _AFFIRMATIVE_CONFIRMATION_TOKENS:
83
+ return "State updated."
84
+ return _summarize_pending_confirmation_update(pending)
85
+
86
+
87
+ def summarize_confirmation_update_from_checkpoint(
88
+ user_input: str, checkpoint: object
89
+ ) -> str:
90
+ pending = checkpoint.get("pending") if isinstance(checkpoint, dict) else None
91
+ return summarize_confirmation_update(user_input, pending)