From dbe0014fb565e16ef6ae932411541d38015a7327 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Wed, 15 Jul 2026 01:26:15 -0400 Subject: [PATCH] chore: chore: sync directive drafter integrations with latest releases --- pyproject.toml | 6 +-- .../prompt_construction/litellm/README.md | 7 +++ .../litellm/with_directive_drafter.py | 2 +- .../litellm_proxy/README.md | 9 +++- ...ler_precall_hook_with_directive_drafter.py | 2 +- .../openwebui_pipe/README.md | 25 ++++++----- .../open_webui_pipe_with_directive_drafter.py | 10 ++--- ...st_litellm_proxy_with_directive_drafter.py | 32 ++++++++++++++ .../test_litellm_with_directive_drafter.py | 36 +++++++++++++++- ...t_openwebui_pipe_with_directive_drafter.py | 43 +++++++++++++++++++ .../nextjs/with_drafter/README.md | 2 + .../nextjs/with_drafter/app/api/chat/route.ts | 2 +- .../nextjs/with_drafter/package-lock.json | 8 ++-- .../nextjs/with_drafter/package.json | 2 +- .../nextjs/with_drafter/tests/smoke.test.mjs | 12 ++++++ .../starter_apps/node/with_drafter/README.md | 2 + .../node/with_drafter/package-lock.json | 8 ++-- .../node/with_drafter/package.json | 2 +- .../starter_apps/node/with_drafter/server.ts | 2 +- .../node/with_drafter/tests/smoke.test.mjs | 12 ++++++ uv.lock | 12 +++--- 21 files changed, 194 insertions(+), 42 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac12587..54855b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,12 +47,12 @@ Issues = "https://github.com/rlippmann/context-compiler-example-integrations/iss [project.optional-dependencies] all = [ "chromadb", - "context-compiler-directive-drafter", + "context-compiler-directive-drafter>=0.1.2", "fastapi", "litellm", ] drafter = [ - "context-compiler-directive-drafter", + "context-compiler-directive-drafter>=0.1.2", ] fastapi = [ "fastapi", @@ -67,7 +67,7 @@ retrieval = [ [dependency-groups] dev = [ "chromadb", - "context-compiler-directive-drafter", + "context-compiler-directive-drafter>=0.1.2", "fastapi", "httpx2>=2.5.0", "httpx>=0.28.1", diff --git a/python/examples/prompt_construction/litellm/README.md b/python/examples/prompt_construction/litellm/README.md index 36a95dd..579a585 100644 --- a/python/examples/prompt_construction/litellm/README.md +++ b/python/examples/prompt_construction/litellm/README.md @@ -20,6 +20,8 @@ with LiteLLM: - if it cannot produce a validated directive, behavior stays equivalent to the compiler-only flow - pending clarification bypasses directive drafting and sends the raw reply back to `engine.step(...)` +Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow. + ## Premise and policy In these prompt-construction examples: @@ -60,6 +62,8 @@ For `with_directive_drafter.py`: pip install "context-compiler-example-integrations[all]" ``` +That variant requires `context-compiler-directive-drafter>=0.1.2`. + ## Quickstart (copy/paste) ```shell @@ -163,6 +167,9 @@ In the related schema-selection example: - Near-miss passthrough (`with_directive_drafter.py`): - `set premise to concise replies` is not rewritten by the directive drafter and is passed through unchanged. - Engine returns clarify (`Did you mean 'set premise concise replies'?`). +- Compound directives (`with_directive_drafter.py`): + - `use docker and prohibit peanuts` returns a local clarify asking for separate directives. + - No authoritative state is mutated and no downstream model call is made for that turn. - Lifecycle enforcement (both): - `change premise to formal tone` with no premise -> clarify (`set premise ...` first). - Conflict behavior (both): diff --git a/python/examples/prompt_construction/litellm/with_directive_drafter.py b/python/examples/prompt_construction/litellm/with_directive_drafter.py index 3befa17..eafc531 100644 --- a/python/examples/prompt_construction/litellm/with_directive_drafter.py +++ b/python/examples/prompt_construction/litellm/with_directive_drafter.py @@ -272,7 +272,7 @@ def _llm_fallback_preprocess(message: str, state: State) -> str | None: except Exception: return None - parsed = parse_preprocessor_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output) if parsed is None: return None return parsed diff --git a/python/reference_integrations/litellm_proxy/README.md b/python/reference_integrations/litellm_proxy/README.md index 1d62404..54dddcc 100644 --- a/python/reference_integrations/litellm_proxy/README.md +++ b/python/reference_integrations/litellm_proxy/README.md @@ -40,6 +40,8 @@ Optional directive-drafter behavior: - Forwarded upstream request messages are not rewritten except for the injected compiler system message. +Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow. + ## Session mode and session keys The reference hooks support two explicit modes: @@ -103,6 +105,8 @@ For `context_compiler_precall_hook_with_directive_drafter.py`: pip install "context-compiler-example-integrations[all]" ``` +That variant requires `context-compiler-directive-drafter>=0.1.2`. + For the opt-in runtime smoke test, install the proxy runtime extras: ```shell @@ -201,7 +205,7 @@ export PREPROCESSOR_PROMPT_PROFILE=default `PREPROCESSOR_MODEL` is optional and defaults to `MODEL`. For heuristic-first usage, keep `PREPROCESSOR_PROMPT_PROFILE=default`. -Use `llama` only for LLM-only preprocessing with Llama-family models. +Use `llama` only for LLM-only fallback drafting with Llama-family models. ## Notes @@ -212,6 +216,9 @@ Use `llama` only for LLM-only preprocessing with Llama-family models. not silently reset state. - In the directive-drafter hook, drafter state context now comes from restored checkpoint state rather than transcript-prefix reconstruction. +- Compound directive-shaped input such as `use docker and prohibit peanuts` + should produce a local clarify response telling the user to submit each + directive separately, without mutating saved state or forwarding upstream. ## Troubleshooting diff --git a/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py b/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py index 8c7a5a0..b6ba3ee 100644 --- a/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +++ b/python/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py @@ -169,7 +169,7 @@ def _llm_fallback_preprocess(message: str, state: State) -> str | None: except Exception: return None - parsed = parse_preprocessor_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output) if parsed is None: return None return parsed diff --git a/python/reference_integrations/openwebui_pipe/README.md b/python/reference_integrations/openwebui_pipe/README.md index 3157a3b..06182ca 100644 --- a/python/reference_integrations/openwebui_pipe/README.md +++ b/python/reference_integrations/openwebui_pipe/README.md @@ -1,8 +1,8 @@ # Open WebUI Pipe Integration Saved compiler state changes which turns the pipe handles locally and what it -forwards downstream. These examples show that Open WebUI pipe behavior with and -without directive-drafter preprocessing. +forwards downstream. These examples show Open WebUI pipe behavior with and +without Directive Drafter preprocessing. ## Core behavior @@ -35,8 +35,8 @@ shapes the downstream request. ## Files -- `open_webui_pipe.py`: basic integration, no directive-drafter layer (recommended/default). -- `open_webui_pipe_with_directive_drafter.py`: optional/experimental directive-drafter layer (rule-based check first, then optional model fallback) before `engine.step(...)`. +- `open_webui_pipe.py`: basic integration, no Directive Drafter layer (recommended/default). +- `open_webui_pipe_with_directive_drafter.py`: optional/experimental Directive Drafter layer (heuristic check first, then optional model fallback) before `engine.step(...)`. ## Setup @@ -64,11 +64,13 @@ These examples require `context-compiler>=0.8.3`. If using `open_webui_pipe_with_directive_drafter.py`: - Install directive-drafter support if needed: - `pip install "context-compiler>=0.8.3" "context-compiler-directive-drafter>=0.1.0"` + `pip install "context-compiler>=0.8.3" "context-compiler-directive-drafter>=0.1.2"` - Set `PREPROCESSOR_PROMPT_PROFILE=default` for heuristic-first behavior -- Optionally set `PREPROCESSOR_MODEL_ID` to use a separate fallback model +- Optionally set `PREPROCESSOR_MODEL_ID` to use a separate fallback drafting model - If `PREPROCESSOR_MODEL_ID` is unset, fallback uses `BASE_MODEL_ID` -- Use `llama` only for LLM-only preprocessing with Llama-family models +- Use `llama` only for LLM-only fallback drafting with Llama-family models + +Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow. ### Docker/manual install fallback @@ -80,7 +82,7 @@ If frontmatter dependency installs are disabled, offline, or unavailable: 1. Install the package manually: - Minimal pipe: `pip install "context-compiler>=0.8.3"` -- Directive-drafter pipe: `pip install "context-compiler>=0.8.3" "context-compiler-directive-drafter>=0.1.0"` +- Directive Drafter pipe: `pip install "context-compiler>=0.8.3" "context-compiler-directive-drafter>=0.1.2"` 1. Import and enable the function in Open WebUI, then configure valves. @@ -125,16 +127,17 @@ Advanced check: ### Directive-drafter pipe -Use this pipe when you want the same runtime behavior plus directive-drafter preprocessing. +Use this pipe when you want the same runtime behavior plus Directive Drafter preprocessing. Suggested verification: - Send `use docker` and confirm you get `State updated: Use docker.` with trace showing a local turn - Send `set premise to concise replies` and confirm Open WebUI clarifies locally with `Use 'set premise '.` - Send `please use docker` and confirm either: - - the directive drafter converts it into a local state update, or + - the Directive Drafter converts it into a local state update, or - trace shows the turn followed the normal compiler path without a silent state change - Send `use kubectl instead of docker`, then reply `yes`, and confirm the saved clarification flow resumes locally +- Send `use docker and prohibit peanuts` and confirm the pipe responds locally that multiple directives are not supported and must be submitted separately - Send a normal prompt such as `what should I run?` and confirm trace shows a forwarded turn with compiler state included Advanced check: @@ -222,7 +225,7 @@ These examples support both sync (`0.8.x`) and async (`0.9.x`) user lookup. - `PREPROCESSOR_MODEL_ID must not match the selected pipe model id`: choose a real backend model id, not the pipe model id itself. - `PREPROCESSOR_MODEL_ID is invalid or not configured in Open WebUI`: the fallback route hit a missing model; fix the configured fallback model or unset it to reuse `BASE_MODEL_ID`. - `ALLOW_MISSING_BASE_MODEL_FOR_DEBUG=true`: directive-only updates still run locally, but passthrough returns a deterministic debug message instead of calling a downstream model. -- imports fail after function upload: install `context-compiler>=0.8.3` in the Open WebUI runtime, and add `context-compiler-directive-drafter>=0.1.0` only for the directive-drafter pipe, because the copied function runs from a temp/cached location. +- imports fail after function upload: install `context-compiler>=0.8.3` in the Open WebUI runtime, and add `context-compiler-directive-drafter>=0.1.2` only for the Directive Drafter pipe, because the copied function runs from a temp/cached location. ## Fallback notes diff --git a/python/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py b/python/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py index 608a8aa..63845d5 100644 --- a/python/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +++ b/python/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py @@ -3,7 +3,7 @@ author: rlippmann author_url: https://github.com/rlippmann/context-compiler-example-integrations version: 0.9.4 -requirements: context-compiler>=0.8.3, context-compiler-directive-drafter>=0.1.0 +requirements: context-compiler>=0.8.3, context-compiler-directive-drafter>=0.1.2 Open WebUI integration with Context Compiler directive drafter. @@ -97,7 +97,7 @@ def _is_directive_shaped_input(message: str) -> bool: def _prompt_file_path(profile: str) -> Traversable: - # Runtime prompt selection for fallback precompilation: + # Runtime prompt selection for fallback drafting: # - default: most instruction-following models # - llama: models that need tighter prompt guidance if profile == "llama": @@ -450,12 +450,12 @@ class Valves(BaseModel): PREPROCESSOR_MODEL_ID: str | None = Field( default=None, description=( - "Optional model id for fallback precompilation (defaults to BASE_MODEL_ID)." + "Optional model id for fallback drafting (defaults to BASE_MODEL_ID)." ), ) PREPROCESSOR_PROMPT_PROFILE: Literal["default", "llama"] = Field( default="default", - description="Prompt profile for LLM fallback precompilation.", + description="Prompt profile for LLM fallback drafting.", ) ALLOW_MISSING_BASE_MODEL_FOR_DEBUG: bool = Field( default=False, @@ -730,7 +730,7 @@ async def _llm_fallback_preprocess( return None, normalized_error raw_output = _extract_completion_content(response) - parsed = parse_preprocessor_output(raw_output, source_input=message) + parsed = parse_preprocessor_output(raw_output) if parsed is None: return None, None return parsed, None diff --git a/python/tests/test_litellm_proxy_with_directive_drafter.py b/python/tests/test_litellm_proxy_with_directive_drafter.py index afd0758..f24dbbd 100644 --- a/python/tests/test_litellm_proxy_with_directive_drafter.py +++ b/python/tests/test_litellm_proxy_with_directive_drafter.py @@ -344,6 +344,38 @@ def test_forwarded_messages_keep_original_user_prompt_text(monkeypatch) -> None: assert data["messages"][1:] == original_messages +def test_compound_directives_block_upstream_and_do_not_mutate_state( + monkeypatch, +) -> None: + module = _load_module(monkeypatch, "litellm_proxy_with_drafter_compound") + module.CHECKPOINT_STORE.clear() + hook = module.ContextCompilerPreCallHookWithPreprocessor() + monkeypatch.setattr( + module, + "_preprocess_last_user_message", + lambda _message, _state: "use docker and prohibit peanuts", + ) + data = { + "model": "demo", + "context_compiler_mode": "persistent", + "context_compiler_session_key": "chat-compound", + "messages": [ + {"role": "user", "content": "please use docker and prohibit peanuts"} + ], + } + + result = asyncio.run(hook.async_pre_call_hook(None, None, data, "completion")) + + assert result == ( + "Multiple directives are not supported in one input.\n" + "Submit each directive separately." + ) + checkpoint = module.CHECKPOINT_STORE.load("chat-compound") + assert checkpoint is not None + assert checkpoint["authoritative_state"]["policies"] == {} + assert checkpoint.get("pending") is None + + def test_no_removed_replay_api_remains(monkeypatch) -> None: module = _load_module(monkeypatch, "litellm_proxy_with_drafter_no_replay") diff --git a/python/tests/test_litellm_with_directive_drafter.py b/python/tests/test_litellm_with_directive_drafter.py index 36893f4..bccf171 100644 --- a/python/tests/test_litellm_with_directive_drafter.py +++ b/python/tests/test_litellm_with_directive_drafter.py @@ -266,7 +266,9 @@ def completion(**kwargs: Any) -> dict[str, object]: assert seen["model"] == "openai/preprocessor-model" -def test_fallback_rejects_premise_near_miss_rewrites(monkeypatch) -> None: +def test_fallback_accepts_structurally_valid_output_without_source_awareness( + monkeypatch, +) -> None: monkeypatch.setenv("OPENAI_API_KEY", "dummy") monkeypatch.setenv("MODEL", "openai/main-model") monkeypatch.delenv("PREPROCESSOR_MODEL", raising=False) @@ -281,7 +283,10 @@ def test_fallback_rejects_premise_near_miss_rewrites(monkeypatch) -> None: ) monkeypatch.setattr(module, "render_prompt", lambda *_: "prompt") - assert module._llm_fallback_preprocess("set premise to concise replies", {}) is None + assert ( + module._llm_fallback_preprocess("set premise to concise replies", {}) + == "set premise concise replies" + ) def test_directive_shaped_malformed_inputs_skip_fallback(monkeypatch) -> None: @@ -315,6 +320,33 @@ def downstream(_messages: list[dict[str, str]]) -> str: assert downstream_calls == 0 +def test_compound_directives_stay_local_and_do_not_call_downstream(monkeypatch) -> None: + downstream_calls = 0 + + def downstream(_messages: list[dict[str, str]]) -> str: + nonlocal downstream_calls + downstream_calls += 1 + raise AssertionError("downstream model should not be called") + + monkeypatch.setattr(module, "_call_litellm", downstream) + monkeypatch.setattr( + module, + "_preprocess_user_input", + lambda _message, _state: "use docker and prohibit peanuts", + ) + + result = module.handle_turn( + "please use docker and prohibit peanuts", + create_engine(), + ) + + assert result == ( + "Multiple directives are not supported in one input.\n" + "Submit each directive separately." + ) + assert downstream_calls == 0 + + @pytest.mark.parametrize( ("confirmation", "expected_policies", "expected_response"), [ diff --git a/python/tests/test_openwebui_pipe_with_directive_drafter.py b/python/tests/test_openwebui_pipe_with_directive_drafter.py index 7d10363..6d5a894 100644 --- a/python/tests/test_openwebui_pipe_with_directive_drafter.py +++ b/python/tests/test_openwebui_pipe_with_directive_drafter.py @@ -265,6 +265,49 @@ async def no_draft(*args, **kwargs): assert forwarded == [] +def test_compound_directives_stay_local_and_do_not_call_downstream(monkeypatch) -> None: + module = _load_module("owui_with_drafter_compound", monkeypatch) + forwarded: list[dict[str, object]] = [] + + async def forward( + _: object, payload: dict[str, object], __: object + ) -> dict[str, object]: + forwarded.append(payload) + raise AssertionError("downstream model should not be called") + + module.generate_chat_completion = forward + pipe = module.Pipe() + pipe.valves.BASE_MODEL_ID = "base-model" + pipe.valves.PREPROCESSOR_MODEL_ID = "prep-model" + + async def compound_draft(*args, **kwargs): + return "use docker and prohibit peanuts", None + + monkeypatch.setattr(module.Pipe, "_preprocess_user_input", compound_draft) + result = asyncio.run( + pipe.pipe( + { + "model": "pipe-model", + "messages": [ + { + "role": "user", + "content": "please use docker and prohibit peanuts", + } + ], + }, + __user__={"id": "u1"}, + __request__=object(), + __chat_id__="chat-compound", + ) + ) + + assert result == ( + "Multiple directives are not supported in one input.\n" + "Submit each directive separately." + ) + assert forwarded == [] + + def test_passthrough_injects_exactly_one_cc_state_system_message_when_state_exists( monkeypatch, ) -> None: diff --git a/typescript/starter_apps/nextjs/with_drafter/README.md b/typescript/starter_apps/nextjs/with_drafter/README.md index c121f40..54b58c5 100644 --- a/typescript/starter_apps/nextjs/with_drafter/README.md +++ b/typescript/starter_apps/nextjs/with_drafter/README.md @@ -8,6 +8,8 @@ material. The enforcement point is still request construction: the route restores saved state, validates drafted directive input, and builds the request payload the host would send onward. +This starter uses `@rlippmann/context-compiler-directive-drafter@^0.1.2`. + ## Files - [app/api/chat/route.ts](app/api/chat/route.ts) - route handler with safe drafter handoff diff --git a/typescript/starter_apps/nextjs/with_drafter/app/api/chat/route.ts b/typescript/starter_apps/nextjs/with_drafter/app/api/chat/route.ts index 4ed2a78..20a6c58 100644 --- a/typescript/starter_apps/nextjs/with_drafter/app/api/chat/route.ts +++ b/typescript/starter_apps/nextjs/with_drafter/app/api/chat/route.ts @@ -80,7 +80,7 @@ function resolveEngineInput(engine: ReturnType, userInput: return userInput; } - const validated = validatePreprocessorOutput(heuristic.directive, { sourceInput: userInput }); + const validated = validatePreprocessorOutput(heuristic.directive); if (validated.classification === PREPROCESS_OUTCOME_DIRECTIVE && validated.output !== null) { return validated.output; } diff --git a/typescript/starter_apps/nextjs/with_drafter/package-lock.json b/typescript/starter_apps/nextjs/with_drafter/package-lock.json index ec8a793..701d8b0 100644 --- a/typescript/starter_apps/nextjs/with_drafter/package-lock.json +++ b/typescript/starter_apps/nextjs/with_drafter/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "@rlippmann/context-compiler": "^0.8.2", - "@rlippmann/context-compiler-directive-drafter": "^0.1.0", + "@rlippmann/context-compiler-directive-drafter": "^0.1.2", "next": "^15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -638,9 +638,9 @@ "license": "Apache-2.0" }, "node_modules/@rlippmann/context-compiler-directive-drafter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@rlippmann/context-compiler-directive-drafter/-/context-compiler-directive-drafter-0.1.0.tgz", - "integrity": "sha512-pXgcOkXVmzFSh3hHWR39kQ6UG6k4i10n6CTaJB2yPqKlyVcAgINf1b4LsExz+b+aqzFZfl75VSzToQ3L7kux2A==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@rlippmann/context-compiler-directive-drafter/-/context-compiler-directive-drafter-0.1.2.tgz", + "integrity": "sha512-6DdEZMYrEpWVVBYfo4QjOHS5ORQktZCZRROc47MRh7Q5qXxXyNuZBIdzqGv1bb0e8O9aO+k19qYzlkCjDY8wxg==", "license": "Apache-2.0" }, "node_modules/@swc/helpers": { diff --git a/typescript/starter_apps/nextjs/with_drafter/package.json b/typescript/starter_apps/nextjs/with_drafter/package.json index b37349e..f28833f 100644 --- a/typescript/starter_apps/nextjs/with_drafter/package.json +++ b/typescript/starter_apps/nextjs/with_drafter/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@rlippmann/context-compiler": "^0.8.2", - "@rlippmann/context-compiler-directive-drafter": "^0.1.0", + "@rlippmann/context-compiler-directive-drafter": "^0.1.2", "next": "^15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/typescript/starter_apps/nextjs/with_drafter/tests/smoke.test.mjs b/typescript/starter_apps/nextjs/with_drafter/tests/smoke.test.mjs index f081064..c3da1d7 100644 --- a/typescript/starter_apps/nextjs/with_drafter/tests/smoke.test.mjs +++ b/typescript/starter_apps/nextjs/with_drafter/tests/smoke.test.mjs @@ -140,3 +140,15 @@ test("saved premise appears in returned system prompt", async () => { /PREMISE:\ndraft is a board update summarizing quarterly results/ ); }); + +test("compound directives stay local and ask for separate inputs", async () => { + const result = await postJson({ + sessionId: "nextjs-drafter-compound", + input: "use docker and prohibit peanuts" + }); + + assert.equal(result.status, 200); + assert.equal(result.json.kind, "clarify"); + assert.match(result.json.promptToUser, /multiple directives/i); + assert.match(result.json.promptToUser, /submit each directive separately/i); +}); diff --git a/typescript/starter_apps/node/with_drafter/README.md b/typescript/starter_apps/node/with_drafter/README.md index a1ea8b2..09bb4b4 100644 --- a/typescript/starter_apps/node/with_drafter/README.md +++ b/typescript/starter_apps/node/with_drafter/README.md @@ -8,6 +8,8 @@ material. `@rlippmann/context-compiler-directive-drafter` can help recognize directive-shaped input, but the compiler remains the only authority over saved state. +This starter uses `@rlippmann/context-compiler-directive-drafter@^0.1.2`. + ## Files - [server.ts](server.ts) - minimal chat endpoint with checkpoint persistence and drafter handoff diff --git a/typescript/starter_apps/node/with_drafter/package-lock.json b/typescript/starter_apps/node/with_drafter/package-lock.json index b417eac..88521a8 100644 --- a/typescript/starter_apps/node/with_drafter/package-lock.json +++ b/typescript/starter_apps/node/with_drafter/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "@rlippmann/context-compiler": "^0.8.2", - "@rlippmann/context-compiler-directive-drafter": "^0.1.0" + "@rlippmann/context-compiler-directive-drafter": "^0.1.2" }, "devDependencies": { "@types/node": "^24.10.0", @@ -466,9 +466,9 @@ "license": "Apache-2.0" }, "node_modules/@rlippmann/context-compiler-directive-drafter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@rlippmann/context-compiler-directive-drafter/-/context-compiler-directive-drafter-0.1.0.tgz", - "integrity": "sha512-pXgcOkXVmzFSh3hHWR39kQ6UG6k4i10n6CTaJB2yPqKlyVcAgINf1b4LsExz+b+aqzFZfl75VSzToQ3L7kux2A==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@rlippmann/context-compiler-directive-drafter/-/context-compiler-directive-drafter-0.1.2.tgz", + "integrity": "sha512-6DdEZMYrEpWVVBYfo4QjOHS5ORQktZCZRROc47MRh7Q5qXxXyNuZBIdzqGv1bb0e8O9aO+k19qYzlkCjDY8wxg==", "license": "Apache-2.0" }, "node_modules/@types/node": { diff --git a/typescript/starter_apps/node/with_drafter/package.json b/typescript/starter_apps/node/with_drafter/package.json index 550bb16..2402cbd 100644 --- a/typescript/starter_apps/node/with_drafter/package.json +++ b/typescript/starter_apps/node/with_drafter/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@rlippmann/context-compiler": "^0.8.2", - "@rlippmann/context-compiler-directive-drafter": "^0.1.0" + "@rlippmann/context-compiler-directive-drafter": "^0.1.2" }, "devDependencies": { "@types/node": "^24.10.0", diff --git a/typescript/starter_apps/node/with_drafter/server.ts b/typescript/starter_apps/node/with_drafter/server.ts index 29bff2f..10993b6 100644 --- a/typescript/starter_apps/node/with_drafter/server.ts +++ b/typescript/starter_apps/node/with_drafter/server.ts @@ -90,7 +90,7 @@ function resolveEngineInput(engine: ReturnType, userInput: return userInput; } - const parsedDirective = parsePreprocessorOutput(heuristic.directive, { sourceInput: userInput }); + const parsedDirective = parsePreprocessorOutput(heuristic.directive); return parsedDirective ?? userInput; } diff --git a/typescript/starter_apps/node/with_drafter/tests/smoke.test.mjs b/typescript/starter_apps/node/with_drafter/tests/smoke.test.mjs index 14d27c3..1f8c84d 100644 --- a/typescript/starter_apps/node/with_drafter/tests/smoke.test.mjs +++ b/typescript/starter_apps/node/with_drafter/tests/smoke.test.mjs @@ -105,3 +105,15 @@ test("unknown or unsafe drafter output falls back to raw input", async () => { assert.equal(result.payload.kind, "clarify"); assert.match(result.payload.promptToUser, /set premise concise replies/i); }); + +test("compound directives stay local and ask for separate inputs", async () => { + const result = await handleChatBody({ + sessionId: "node-drafter-compound", + input: "use docker and prohibit peanuts" + }); + + assert.equal(result.status, 200); + assert.equal(result.payload.kind, "clarify"); + assert.match(result.payload.promptToUser, /multiple directives/i); + assert.match(result.payload.promptToUser, /submit each directive separately/i); +}); diff --git a/uv.lock b/uv.lock index 442155c..4cba4ca 100644 --- a/uv.lock +++ b/uv.lock @@ -667,14 +667,14 @@ wheels = [ [[package]] name = "context-compiler-directive-drafter" -version = "0.1.0" +version = "0.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "context-compiler" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/80/e2715af893fb9677f785b3e4521870891ed36fab4af2a3fa8fd3a6352b26/context_compiler_directive_drafter-0.1.0.tar.gz", hash = "sha256:f8b26efeebedc66821a80f7aa3d436066bc2073918d62f9e02b75ac44177a67c", size = 103252, upload-time = "2026-06-04T09:55:21.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/37/da53095be377487fb24dae5d5bcefee3f3cb56e193f7e51f62c3f4ccd187/context_compiler_directive_drafter-0.1.2.tar.gz", hash = "sha256:612b295e827f9019e1dab9ca16979bbaf948d6d7b52e11d4cb723ffe8a3675bb", size = 77122, upload-time = "2026-07-14T07:21:30.03Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/38/0d75db6b253e4dab85a7c2c3225ed1ecd382b6e084540b41c692b5545e33/context_compiler_directive_drafter-0.1.0-py3-none-any.whl", hash = "sha256:6142022cbcc85d8edff252d7aa2aeb0f9876fc8e800df32c6828aa884fea2c62", size = 48809, upload-time = "2026-06-04T09:55:20.157Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a1/5523810cbc5649bf047b27809186a9f89d9d74de7d1d5979410b57fce609/context_compiler_directive_drafter-0.1.2-py3-none-any.whl", hash = "sha256:56ed34c01544e69ab660b0e9cfb5032800c1ccb9afea2ecc0d827476af9be7d4", size = 21428, upload-time = "2026-07-14T07:21:28.819Z" }, ] [[package]] @@ -727,8 +727,8 @@ requires-dist = [ { name = "chromadb", marker = "extra == 'all'" }, { name = "chromadb", marker = "extra == 'retrieval'" }, { name = "context-compiler", specifier = ">=0.8.3" }, - { name = "context-compiler-directive-drafter", marker = "extra == 'all'" }, - { name = "context-compiler-directive-drafter", marker = "extra == 'drafter'" }, + { name = "context-compiler-directive-drafter", marker = "extra == 'all'", specifier = ">=0.1.2" }, + { name = "context-compiler-directive-drafter", marker = "extra == 'drafter'", specifier = ">=0.1.2" }, { name = "fastapi", marker = "extra == 'all'" }, { name = "fastapi", marker = "extra == 'fastapi'" }, { name = "litellm", marker = "extra == 'all'" }, @@ -739,7 +739,7 @@ provides-extras = ["all", "drafter", "fastapi", "litellm", "retrieval"] [package.metadata.requires-dev] dev = [ { name = "chromadb" }, - { name = "context-compiler-directive-drafter" }, + { name = "context-compiler-directive-drafter", specifier = ">=0.1.2" }, { name = "fastapi" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "httpx2", specifier = ">=2.5.0" },