feat: prepare 2.2.0 with UI and execution fixes
Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
This commit is contained in:
1 parent
83bcfd54d5
commit
fac4ccb760
146 files changed
+5689
-2033
No files matched your search
@@ -18,6 +18,7 @@ from artemis.llm.diagnostics import (
|
||||
)
|
||||
from artemis.llm.router import ModelEndpoint
|
||||
from artemis.services import llm
|
||||
from artemis.runtime.cancellation import CancellationReason
|
||||
|
||||
|
||||
class Engine:
|
||||
@@ -121,6 +122,10 @@ async def test_headers_without_body_timeout_then_retry_keeps_evidence(monkeypatc
|
||||
assert failed["usage"] is None and "first_output_at" not in failed
|
||||
assert final["request_id"] == failed["request_id"]
|
||||
assert final["attempt_id"] != failed["attempt_id"] and final["attempt"] == 2
|
||||
assert final["previous_attempt_id"] == failed["attempt_id"]
|
||||
retry = next(r["payload"] for r in engine.records if r["name"] == "llm_retry")
|
||||
assert retry["request_id"] == failed["request_id"]
|
||||
assert retry["attempt_id"] == failed["attempt_id"]
|
||||
assert final["phase"] == "completed" and final["usage"]["total_tokens"] == 12
|
||||
assert (
|
||||
final["started_at"] <= final["headers_at"] <= final["first_output_at"] <= final["ended_at"]
|
||||
@@ -226,6 +231,72 @@ async def test_concurrent_requests_do_not_mix_server_ids():
|
||||
assert second["server_request_id"] == "two"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_diagnostic_ids_survive_streaming_and_structured_completion_without_header_bodies():
|
||||
engine = Engine()
|
||||
attempt = RequestAttempt(
|
||||
engine, request_id="local", attempt=1, timeout=300, max_retries=1, metadata={}
|
||||
)
|
||||
token = ACTIVE_ATTEMPT.set(attempt)
|
||||
traceparent = "00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01"
|
||||
try:
|
||||
request = httpx.Request(
|
||||
"POST", "https://example.test/responses", headers={"x-client-request-id": "caller-id"}
|
||||
)
|
||||
await observe_request_headers(request)
|
||||
await observe_response_headers(
|
||||
httpx.Response(
|
||||
200,
|
||||
headers={
|
||||
"x-request-id": "server-id",
|
||||
"x-trace-id": "gateway-id",
|
||||
"traceparent": traceparent,
|
||||
"set-cookie": "private cookie",
|
||||
"authorization": "private credential",
|
||||
},
|
||||
)
|
||||
)
|
||||
attempt.chunk(AIMessageChunk(content="hello", response_metadata={"id": "resp-first"}))
|
||||
attempt.finish(
|
||||
"completed",
|
||||
response={
|
||||
"raw": AIMessageChunk(
|
||||
content="hello", response_metadata={"id": "resp-final", "status": "completed"}
|
||||
),
|
||||
"parsed": {"private": "business details"},
|
||||
},
|
||||
)
|
||||
finally:
|
||||
ACTIVE_ATTEMPT.reset(token)
|
||||
assert request.headers["x-client-request-id"] == "caller-id"
|
||||
assert attempt.data["client_request_id"] == "caller-id"
|
||||
assert attempt.data["server_request_id"] == "server-id"
|
||||
assert attempt.data["server_trace_id"] == "gateway-id"
|
||||
assert attempt.data["traceparent"] == traceparent
|
||||
assert attempt.data["response_id"] == "resp-final"
|
||||
assert attempt.data["response_status"] == "completed"
|
||||
assert "private" not in json.dumps(engine.records, default=str)
|
||||
|
||||
|
||||
def test_invalid_or_overlong_correlation_headers_are_omitted():
|
||||
attempt = RequestAttempt(
|
||||
Engine(), request_id="local", attempt=1, timeout=300, max_retries=1, metadata={}
|
||||
)
|
||||
attempt.headers(
|
||||
httpx.Response(
|
||||
200,
|
||||
headers={
|
||||
"x-request-id": "x" * 257,
|
||||
"x-trace-id": "not an id",
|
||||
"traceparent": "not a trace",
|
||||
},
|
||||
)
|
||||
)
|
||||
assert attempt.data["server_request_id"] is None
|
||||
assert attempt.data["server_trace_id"] is None
|
||||
assert "traceparent" not in attempt.data
|
||||
|
||||
|
||||
def test_error_redaction_and_image_headers():
|
||||
import base64
|
||||
import io
|
||||
@@ -372,11 +443,14 @@ async def test_cancelling_retry_wait_does_not_leave_a_live_request(monkeypatch):
|
||||
|
||||
task = asyncio.create_task(llm._run_with_recovery(timeout, max_retries=1))
|
||||
await asyncio.wait_for(waiting.wait(), 2)
|
||||
task.cancel()
|
||||
task.cancel(CancellationReason.GUIDANCE_REPLACED)
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
assert engine.records[-1]["payload"]["phase"] == "cancelled"
|
||||
assert "模型请求" in engine.records[-1]["payload"]["error"]
|
||||
assert engine.records[-1]["payload"]["cancel_reason"] == "guidance_replaced"
|
||||
assert engine.records[-1]["payload"]["cancel_phase"] == "retry_wait"
|
||||
assert "timeout_scope" not in engine.records[-1]["payload"]
|
||||
|
||||
|
||||
def test_diagnostic_api_limits_preview_but_downloads_complete_history(tmp_path, monkeypatch):
|
||||
|
||||
Reference in new issue
Block a user