ship: v0.2 P3 API layer + integration #12
+46
-55
@@ -1,77 +1,68 @@
|
||||
# PLAN — Milestone v0.2 Phase 2: Research Engine + Trust Loop
|
||||
# PLAN — Milestone v0.2 Phase 3: API Layer + Integration
|
||||
|
||||
---
|
||||
phase: 2
|
||||
phase: 3
|
||||
milestone: v0.2
|
||||
status: planned
|
||||
must_haves:
|
||||
- "Asyncio state machine walks PLANNING → AWAITING_APPROVAL → SEARCHING → EXTRACTING → REFLECTING → (depth loop) → SYNTHESIZING → VERIFYING → DONE with typed transitions; pause/cancel/resume survive process restarts via durable JSON state (atomic rewrite per transition + cursor)"
|
||||
- "Plan = sub-questions + source universe + outline; run halts at AWAITING_APPROVAL until approved (fake-model plan proven end-to-end)"
|
||||
- "Typed event bus → trace file per run + SSE-ready event stream (query_fired, source_visited, passage_extracted, state_changed, run_done, run_error)"
|
||||
- "Evidence store: visited pages with stable passage IDs; passage extraction; writer context = stored passages only (visited-only constraint by construction)"
|
||||
- "Trust loop: post-run URL liveness pass (live/stale/dead); claim-to-passage quote anchoring (normalize + exact short-circuit + SequenceMatcher ≥ 0.85, weak_match flags); citation ledger JSON per run; exportable verification checklist; confidence labels (single-source vs corroborated)"
|
||||
- "SSRF guard at the fetch boundary (private/loopback/link-local + scheme allowlist) before the engine consumes search results (verify P1 flag, now mandatory)"
|
||||
- "Run-id generator consolidated to the storage adapter's (reduced alphabet)"
|
||||
- "Live capability probing against real endpoints when keys exist (REQ-07/12 complete) — probes surfaced as typed results, never crash the run"
|
||||
- "Zero-API-key: every engine + trust test runs on fakes"
|
||||
- "FastAPI app with routes: POST /api/runs (create), GET /api/methodologies (cards with ETA + cost estimate + why-recommended), GET /api/runs/{id}, POST approve/pause/cancel/resume, GET /api/runs/{id}/events (SSE), GET /api/config (adapter status + probe results + cost counters per G-007)"
|
||||
- "In-memory run registry (single-user, single uvicorn worker) mapping run_id → RunActor; runs execute as asyncio tasks"
|
||||
- "SSE transport: sse-starlette EventSourceResponse streaming the typed event bus (state replay on connect); client disconnect cancels only the subscription, never the run"
|
||||
- "openapi.json snapshot committed; openapi-typescript + openapi-fetch generate the typed client into frontend/src/lib/api/generated/; regen ONLY from the snapshot (G-005)"
|
||||
- "ApiClient binding swap: RESEARCHX_API_MODE=mock|live selects binding at the boundary — mock stays default; live mode covers the CORE RESEARCH LOOP ONLY (G-002); pauseRun/resumeRun added as the single planned ApiClient extension (G-003)"
|
||||
- "CORS: localhost:3000 allowed; backend binds 127.0.0.1"
|
||||
- "ETA/cost heuristics for methodology cards (depth × breadth based)"
|
||||
- "Backend route tests via httpx ASGI transport — zero API keys, fakes injected"
|
||||
personas:
|
||||
- backend-engineer
|
||||
- data-engineer
|
||||
- security-engineer
|
||||
- frontend-engineer
|
||||
- ci-lead-developer
|
||||
requirements: [REQ-07, REQ-12, REQ-13, REQ-14, REQ-15, REQ-16, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22, REQ-23, REQ-24]
|
||||
requirements: [REQ-25, REQ-26, REQ-33]
|
||||
---
|
||||
|
||||
## User-Facing Surface
|
||||
|
||||
Still no UI change in P2 (API + client swap land in P3). The P2 surface is the engine itself, exercisable via tests and a documented `uv run python -m researchx.engine.demo` happy-path script (fake adapters): question → plan → approve → search → extract → reflect → synthesize → verify → DONE, artifacts written to a temp runs dir (state.json, trace.jsonl, report.md, ledger.json, checklist.md).
|
||||
The MVP goes live: with the backend running (`uv run uvicorn researchx.api.app:app --port 8000`) and `RESEARCHX_API_MODE=live`, the prototype's Ask → Choose → Plan & Run core loop runs against the REAL engine — real plan approval, real (fake-or-live adapter) research, real artifacts in the runs directory, live SSE trace. Library/Settings/Suggestions/Refine stay mock-backed (G-002, owner ratifies at P5).
|
||||
|
||||
## Happy Path (P2 — test + demo script, written before execute)
|
||||
## Happy Path (P3 — written before execute)
|
||||
|
||||
1. `cd backend && uv sync && uv run pytest` — all green, zero API keys
|
||||
2. `uv run python -m researchx.engine.demo` — fake-adapter run walks every state, artifacts land in a temp dir
|
||||
3. `uv run python -c "..."` — load state.json: state DONE; ledger.json: citations with health + authority tier; checklist.md exportable
|
||||
4. Pause mid-depth-loop test: cancel task, restart engine from state file, run completes (REQ-15/32 mid-loop seed planted)
|
||||
5. Adversarial seed: fake writer emits a hallucinated URL → verifier flags it dead; quote mismatch → weak_match/mismatch flagged in ledger (REQ-32 seeds, full suite in P4)
|
||||
6. Frontend gates still green (untouched)
|
||||
1. `cd backend && uv run uvicorn researchx.api.app:app --port 8000` → health at `/api/config` (adapter status JSON)
|
||||
2. `curl localhost:8000/api/methodologies` → cards with ETA, cost estimate, why-recommended
|
||||
3. `curl -X POST localhost:8000/api/runs -d '{"question":"...","methodology":"rapid-answer"}'` → run id, state PLANNING→AWAITING_APPROVAL
|
||||
4. `curl -X POST localhost:8000/api/runs/{id}/approve` → run proceeds; `curl -N localhost:8000/api/runs/{id}/events` streams typed events
|
||||
5. `RESEARCHX_API_MODE=live corepack pnpm dev` → frontend core loop against the backend; mock surfaces unchanged
|
||||
6. `corepack pnpm test` — ApiClient boundary tests green in both modes; `uv run pytest` — route tests green
|
||||
|
||||
## UX Acceptance Criteria (P2)
|
||||
## UX Acceptance Criteria (P3)
|
||||
|
||||
- Trust is visible by construction: every claim in report.md carries a citation marker; the ledger names every cited URL with visited_at, passages, health, authority tier
|
||||
- Degradation honest: liveness failures, weak matches, and fallback probes surface in typed data, never silent
|
||||
- No secret value ever reaches a trace, artifact, or error message
|
||||
- A restarted run resumes, never re-plans from scratch (unless depth cursor says so)
|
||||
- Zero component rewrites: only the ApiClient boundary file changes (REQ-54/REQ-26 promise kept)
|
||||
- Live mode is opt-in and obvious; mock remains the default — nobody accidentally spends tokens
|
||||
- Methodology cards show honest ETA/cost estimates (heuristics labeled as estimates)
|
||||
- SSE stream survives page reloads (state replay on connect); closing the tab never kills a run
|
||||
- 3-click path (Ask → Choose → approve plan) unchanged
|
||||
|
||||
## Tasks (waves — dependency-ordered)
|
||||
## Tasks (waves)
|
||||
|
||||
### Wave 1 — Evidence store (data-engineer)
|
||||
- **T-2.1** Evidence models: `researchx/evidence/store.py` — Passage (stable ID: `p-<n>` sequence, url, quote, text), VisitedPage, EvidenceStore (add passage/page, get context bundle for writer: ONLY stored passages, by construction REQ-18); passage extraction from PageContent (paragraph split, min length, dedupe)
|
||||
- **T-2.2** Evidence tests: stable IDs, dedupe, visited-only context (a passage not stored can never appear in writer context)
|
||||
### Wave 1 — API layer (backend-engineer)
|
||||
- **T-3.1** FastAPI app + in-memory run registry + CORS: `researchx/api/app.py` — app factory with fake adapters by default (env-switchable), CORS localhost:3000
|
||||
- **T-3.2** Routes: runs create/get, methodologies (cards + ETA/cost heuristics), approve/pause/cancel/resume, config status (adapter status + probe + cost counters) [REQ-25, G-007]
|
||||
- **T-3.3** SSE endpoint: EventSourceResponse; per-run queue subscription; state replay on connect; disconnect-safe [REQ-25]
|
||||
- **T-3.4** Route tests: httpx ASGI transport; full lifecycle walk over HTTP; SSE event stream assertion; zero keys
|
||||
|
||||
### Wave 2 — Engine (backend-engineer)
|
||||
- **T-2.3** State machine core: `researchx/engine/states.py` (enum + legal transitions table) + `run_state.py` (RunState pydantic: state, cursor, plan, counters; JSON round-trip) — persisted via storage adapter atomically after every transition (REQ-13, REQ-15)
|
||||
- **T-2.4** Event bus: `researchx/engine/events.py` — typed pydantic events (discriminated union on type), EventBus with subscribe/publish, trace writer (append-only .jsonl per run) (REQ-16)
|
||||
- **T-2.5** RunActor: `researchx/engine/actor.py` — one actor per run; walks states; planner call (model adapter, plan = sub-questions + source universe + outline) → AWAITING_APPROVAL halt → approved → SEARCHING (search adapter per dials; query_fired events) → EXTRACTING (fetch + evidence store + SSRF guard; source_visited/passage_extracted events) → REFLECTING depth loop (planner; depth from dials; cursor checkpoint) → SYNTHESIZING (writer; context = evidence bundle ONLY; per-claim markers) → VERIFYING (trust loop) → DONE (persist report + ledger + checklist) (REQ-13, REQ-14, REQ-16, REQ-18)
|
||||
- **T-2.6** Pause/cancel/resume: asyncio.Event pause gate; cancel persists terminal state; resume from state file re-enters at cursor; CancelledError never swallowed (re-raised after shielded persist) (REQ-15)
|
||||
- **T-2.7** Engine tests on fakes: full walk, AWAITING_APPROVAL halt + approve, pause/resume mid-depth-loop, cancel, restart-from-state-file, event ordering, visited-only writer context, zero API keys
|
||||
### Wave 2 — Client swap (frontend-engineer)
|
||||
- **T-3.5** openapi.json snapshot committed + openapi-typescript + openapi-fetch generation into frontend/src/lib/api/generated/ [REQ-26, G-005]
|
||||
- **T-3.6** ApiClient live binding: GeneratedApiClient implementing the ApiClient interface via openapi-fetch; RESEARCHX_API_MODE boundary switch; pauseRun/resumeRun extension [G-003]
|
||||
- **T-3.7** Frontend tests: boundary binding both modes; live-binding contract tests against mocked fetch; component suite stays green (no component changes)
|
||||
|
||||
### Wave 3 — Trust loop (backend-engineer + security-engineer)
|
||||
- **T-2.8** Liveness pass: `researchx/trust/liveness.py` — GET with redirect-follow (10s, size cap from P1 flag), live/stale/dead classification; runs post-run over cited URLs (REQ-19)
|
||||
- **T-2.9** Quote anchoring: `researchx/trust/anchoring.py` — normalize (NFC/casefold/whitespace collapse) + exact-substring short-circuit + SequenceMatcher ≥ 0.85 per-passage; near-threshold (0.80–0.90) flagged weak_match; mismatches flagged never dropped (REQ-20)
|
||||
- **T-2.10** Citation ledger + checklist: `researchx/trust/ledger.py` — per-run ledger JSON (url, visited_at, passages, claims supported, health, authority tier); verification checklist export (markdown); confidence labels single-source vs corroborated (REQ-21, REQ-22, REQ-24)
|
||||
- **T-2.11** SSRF guard: `researchx/trust/url_guard.py` (or adapters/fetch guard) — scheme http/https only; resolve host; refuse private/loopback/link-local/0.0.0.0/IPv6 ULA; wired into every fetch path (search adapter fetch + engine extract) (P1 P1-flag, security persona)
|
||||
- **T-2.12** Trust tests: liveness classification (MockTransport), anchoring thresholds + weak_match + shared-corpus sentence, ledger schema, checklist export, confidence labels, SSRF refusals (private IP, link-local metadata 169.254.169.254, non-http scheme)
|
||||
### Wave 3 — Integration proofs (ci-lead-developer)
|
||||
- **T-3.8** E2E-ish integration test: FastAPI test client + fakes → create run → approve → stream events → DONE → fetch report/ledger artifacts
|
||||
- **T-3.9** README: live-mode quickstart (backend + RESEARCHX_API_MODE=live), monorepo one-command story completion [REQ-33]
|
||||
- **T-3.10** Gates + REQ mapping check: REQ-25/26/33 coverage table in verify commit
|
||||
|
||||
### Wave 4 — Live probing + integration (backend-engineer + ci-lead-developer)
|
||||
- **T-2.13** Live probe wiring: engine run pre-flight probes model adapter when OLLAMA_API_KEY or local endpoint reachable — probe result + structured-output warning stored in run state + trace; probe failure degrades honestly, never crashes the run (REQ-07, REQ-12 complete)
|
||||
- **T-2.14** Demo script + integration proofs: researchx.engine.demo (fake adapters, temp dir); end-to-end integration test pinning artifacts (state DONE, report with markers, ledger health, checklist); run-id consolidation (storage adapter's generator everywhere)
|
||||
- **T-2.15** README: engine architecture section (state machine diagram in text, artifact layout runs/<id>/)
|
||||
## Out of scope for P3
|
||||
- Adversarial suite expansion + real-stack 3-click validation (P4, REQ-17/32/34); UI for library/settings live data (v0.3+, G-002)
|
||||
|
||||
## Out of scope for P2
|
||||
- API routes/SSE transport/OpenAPI client (P3); adversarial suite expansion + E2E (P4); ETA/cost heuristics (P3, REQ-25 adjacent)
|
||||
|
||||
## Verification hooks (consumed by VERIFY)
|
||||
- Gates: backend ruff/mypy strict/pytest; frontend untouched-green
|
||||
- REQ coverage: REQ-07, 12, 13..24 each mapped to ≥1 test or artifact proof
|
||||
- Security: SSRF refusals test-enforced; no value leaks in traces/artifacts; CancelledError discipline
|
||||
- G-006 note: if this phase slips beyond one extension cycle, split into P2a (engine/SSE artifacts) + P2b (trust) patches — requires orchestrator decision commit
|
||||
## Verification hooks
|
||||
- Gates: backend ruff/mypy/pytest (route tests); frontend typecheck/lint/test/build
|
||||
- REQ coverage: REQ-25 (routes + SSE + config), REQ-26 (generated client + swap), REQ-33 (one-command story)
|
||||
- Security: CORS limited; no secrets in snapshots; run-id validation at the API boundary
|
||||
@@ -60,6 +60,20 @@ All keys resolve from env files only (`.env`, `.env.secrets`, `.env.*` — never
|
||||
|
||||
The test suite needs **no** keys: fake model/search adapters (REQ-31) drive every engine test.
|
||||
|
||||
## Live mode (v0.2 P3)
|
||||
|
||||
The frontend runs mock-backed by default (works standalone, zero keys). To run the core research loop against the real backend:
|
||||
|
||||
```bash
|
||||
# terminal 1 — backend (fake adapters by default; live adapters via env-file keys)
|
||||
cd backend && uv run uvicorn researchx.api.app:app --reload --port 8000
|
||||
|
||||
# terminal 2 — frontend in live mode
|
||||
cd frontend && RESEARCHX_API_MODE=live corepack pnpm dev
|
||||
```
|
||||
|
||||
Live mode covers the core loop (Ask → Choose → approve plan → live trace → report + ledger); the library, settings persistence, suggestions, refine, and graph surfaces stay mock-backed until v0.3. The API surface: `POST /api/runs`, `GET /api/methodologies`, run lifecycle (`approve/pause/cancel/resume`), `GET /api/runs/{id}/events` (SSE), `GET /api/config`, report + ledger artifacts.
|
||||
|
||||
## What's in the prototype
|
||||
|
||||
| Screen | What it does |
|
||||
|
||||
@@ -17,6 +17,7 @@ dev = [
|
||||
"mypy>=1.11",
|
||||
"pytest>=8",
|
||||
"pytest-asyncio>=0.23",
|
||||
"httpx>=0.28.1",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@@ -41,4 +42,4 @@ python_version = "3.12"
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["src"]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
testpaths = ["tests"]
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
from researchx.adapters.fakes import CORPUS, FakeModelAdapter, FakeSearchAdapter
|
||||
from researchx.adapters.local_storage import LocalStorageAdapter
|
||||
from researchx.adapters.types import AdapterError, ChatResult
|
||||
from researchx.engine.actor import RunActor, make_run_config
|
||||
from researchx.engine.events import EventBus, RunEvent
|
||||
from researchx.engine.run_state import RunState
|
||||
from researchx.engine.states import RunStateName
|
||||
from researchx.methodologies import registry
|
||||
|
||||
_CANNED_PLAN = (
|
||||
'{"sub_questions": ["What is the research question?"], '
|
||||
'"source_universe": ["web"], "outline": ["approach", "findings", "conclusion"]}'
|
||||
)
|
||||
_CANNED_WRITER = (
|
||||
"# Research Memo\n\n"
|
||||
"The verified core finding [p-1]. A second corroborated observation [p-2].\n"
|
||||
)
|
||||
|
||||
|
||||
class CreateRunBody(BaseModel):
|
||||
question: str = Field(min_length=1)
|
||||
context: str = ""
|
||||
methodology: str
|
||||
dial_overrides: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ApproveBody(BaseModel):
|
||||
plan: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class RunSummary(BaseModel):
|
||||
id: str
|
||||
status: str
|
||||
question: str
|
||||
methodology: str
|
||||
plan: dict[str, Any] | None = None
|
||||
started_at: str
|
||||
|
||||
|
||||
class MethodologyCard(BaseModel):
|
||||
slug: str
|
||||
name: str
|
||||
description: str
|
||||
eta_minutes: int
|
||||
cost_estimate: str
|
||||
why_recommended: str
|
||||
output_shape: str
|
||||
dials: dict[str, Any]
|
||||
|
||||
|
||||
class ConfigStatus(BaseModel):
|
||||
model_adapter: str
|
||||
search_adapter: str
|
||||
storage_dir: str
|
||||
probe: dict[str, Any] | None = None
|
||||
cost_counters: dict[str, int]
|
||||
|
||||
|
||||
_STATUS_MAP = {
|
||||
RunStateName.PLANNING: "planning",
|
||||
RunStateName.AWAITING_APPROVAL: "awaiting_approval",
|
||||
RunStateName.SEARCHING: "searching",
|
||||
RunStateName.EXTRACTING: "extracting",
|
||||
RunStateName.REFLECTING: "reflecting",
|
||||
RunStateName.SYNTHESIZING: "synthesizing",
|
||||
RunStateName.VERIFYING: "verifying",
|
||||
RunStateName.DONE: "done",
|
||||
RunStateName.CANCELLED: "cancelled",
|
||||
RunStateName.FAILED: "cancelled",
|
||||
}
|
||||
|
||||
_ETA_TABLE = {"rapid-answer": (2, "low"), "deep-report": (12, "medium")}
|
||||
|
||||
|
||||
def _cost_estimate(slug: str) -> str:
|
||||
depth, breadth = 1, 1
|
||||
methodology = registry.get(slug)
|
||||
depth = methodology.dials.depth
|
||||
breadth = methodology.dials.breadth
|
||||
units = depth * breadth
|
||||
if units <= 1:
|
||||
return "~5k tokens (estimate)"
|
||||
if units <= 4:
|
||||
return "~25k tokens (estimate)"
|
||||
return "~60k tokens (estimate)"
|
||||
|
||||
|
||||
def _why_recommended(slug: str) -> str:
|
||||
return {
|
||||
"rapid-answer": "One pass, one memo — fastest path to a confident, cited answer.",
|
||||
"deep-report": "Iterative depth with full verification — when the answer must stand up to scrutiny.",
|
||||
}.get(slug, "Methodology-first research.")
|
||||
|
||||
|
||||
class RunRegistry:
|
||||
def __init__(self) -> None:
|
||||
self._actors: dict[str, tuple[RunActor, asyncio.Task[RunState]]] = {}
|
||||
self._buses: dict[str, EventBus] = {}
|
||||
self.cost_counters: dict[str, int] = {"runs_created": 0, "runs_completed": 0}
|
||||
|
||||
def add(self, run_id: str, actor: RunActor, task: asyncio.Task[RunState], bus: EventBus) -> None:
|
||||
self._actors[run_id] = (actor, task)
|
||||
self._buses[run_id] = bus
|
||||
self.cost_counters["runs_created"] += 1
|
||||
|
||||
def actor(self, run_id: str) -> RunActor:
|
||||
entry = self._actors.get(run_id)
|
||||
if entry is None:
|
||||
raise KeyError(run_id)
|
||||
return entry[0]
|
||||
|
||||
def bus(self, run_id: str) -> EventBus:
|
||||
bus = self._buses.get(run_id)
|
||||
if bus is None:
|
||||
raise KeyError(run_id)
|
||||
return bus
|
||||
|
||||
def has(self, run_id: str) -> bool:
|
||||
return run_id in self._actors
|
||||
|
||||
def note_completed(self) -> None:
|
||||
self.cost_counters["runs_completed"] += 1
|
||||
|
||||
|
||||
def create_app(
|
||||
*,
|
||||
storage_dir: Path | None = None,
|
||||
model_adapter: Any = None,
|
||||
search_adapter: Any = None,
|
||||
) -> FastAPI:
|
||||
app = FastAPI(title="ResearchX API", version="0.2.0")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
storage = LocalStorageAdapter(base_dir=storage_dir or Path.cwd() / ".researchx")
|
||||
runs = RunRegistry()
|
||||
model = model_adapter or FakeModelAdapter(
|
||||
canned=[
|
||||
ChatResult(content=_CANNED_PLAN, model="fake-planner"),
|
||||
ChatResult(content=_CANNED_WRITER, model="fake-writer"),
|
||||
]
|
||||
)
|
||||
search = search_adapter or FakeSearchAdapter(CORPUS)
|
||||
|
||||
def _summary(run_id: str) -> RunSummary:
|
||||
actor = runs.actor(run_id)
|
||||
state = actor.state
|
||||
return RunSummary(
|
||||
id=run_id,
|
||||
status=_STATUS_MAP[state.state],
|
||||
question=state.question,
|
||||
methodology=state.methodology_slug,
|
||||
plan=state.plan.model_dump(mode="json") if state.plan else None,
|
||||
started_at=state.created_at.isoformat(),
|
||||
)
|
||||
|
||||
@app.get("/api/config")
|
||||
async def config_status() -> ConfigStatus:
|
||||
return ConfigStatus(
|
||||
model_adapter=type(model).__name__,
|
||||
search_adapter=type(search).__name__,
|
||||
storage_dir=str(storage.run_dir("00000000").parent),
|
||||
probe=None,
|
||||
cost_counters=runs.cost_counters,
|
||||
)
|
||||
|
||||
@app.get("/api/methodologies")
|
||||
async def methodologies() -> list[MethodologyCard]:
|
||||
cards: list[MethodologyCard] = []
|
||||
for methodology in registry.list():
|
||||
eta_minutes, _ = _ETA_TABLE.get(methodology.slug, (5, "low"))
|
||||
cards.append(
|
||||
MethodologyCard(
|
||||
slug=methodology.slug,
|
||||
name=methodology.name,
|
||||
description=methodology.description,
|
||||
eta_minutes=eta_minutes,
|
||||
cost_estimate=_cost_estimate(methodology.slug),
|
||||
why_recommended=_why_recommended(methodology.slug),
|
||||
output_shape=methodology.output_shape,
|
||||
dials=methodology.dials.model_dump(mode="json"),
|
||||
)
|
||||
)
|
||||
return cards
|
||||
|
||||
@app.post("/api/runs", status_code=201)
|
||||
async def create_run(body: CreateRunBody) -> RunSummary:
|
||||
if body.methodology not in registry:
|
||||
raise HTTPException(status_code=404, detail="unknown methodology")
|
||||
try:
|
||||
run_id, config = make_run_config(
|
||||
question=body.question,
|
||||
context=body.context,
|
||||
methodology_slug=body.methodology,
|
||||
overrides=body.dial_overrides,
|
||||
registry=registry,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
except ValidationError as exc:
|
||||
raise HTTPException(status_code=422, detail="invalid dial override") from exc
|
||||
bus = EventBus()
|
||||
actor = RunActor(
|
||||
run_config=config,
|
||||
model=model,
|
||||
search=search,
|
||||
storage=storage,
|
||||
registry=registry,
|
||||
event_bus=bus,
|
||||
)
|
||||
task = asyncio.create_task(actor.run())
|
||||
runs.add(run_id, actor, task, bus)
|
||||
|
||||
async def _watch() -> None:
|
||||
state = await task
|
||||
if state.state == RunStateName.DONE:
|
||||
runs.note_completed()
|
||||
|
||||
asyncio.create_task(_watch())
|
||||
return _summary(run_id)
|
||||
|
||||
@app.get("/api/runs/{run_id}")
|
||||
async def get_run(run_id: str) -> RunSummary:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
return _summary(run_id)
|
||||
|
||||
@app.post("/api/runs/{run_id}/approve")
|
||||
async def approve_run(run_id: str, body: ApproveBody | None = None) -> RunSummary:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
actor = runs.actor(run_id)
|
||||
await actor.approve()
|
||||
return _summary(run_id)
|
||||
|
||||
@app.post("/api/runs/{run_id}/pause")
|
||||
async def pause_run(run_id: str) -> RunSummary:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
runs.actor(run_id).pause()
|
||||
return _summary(run_id)
|
||||
|
||||
@app.post("/api/runs/{run_id}/resume")
|
||||
async def resume_run(run_id: str) -> RunSummary:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
runs.actor(run_id).resume()
|
||||
return _summary(run_id)
|
||||
|
||||
@app.post("/api/runs/{run_id}/cancel")
|
||||
async def cancel_run(run_id: str) -> RunSummary:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
actor = runs.actor(run_id)
|
||||
actor.cancel()
|
||||
actor.resume()
|
||||
return _summary(run_id)
|
||||
|
||||
@app.get("/api/runs/{run_id}/events")
|
||||
async def run_events(run_id: str) -> EventSourceResponse:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
bus = runs.bus(run_id)
|
||||
actor = runs.actor(run_id)
|
||||
terminal = {RunStateName.DONE, RunStateName.CANCELLED, RunStateName.FAILED}
|
||||
|
||||
async def event_stream() -> AsyncIterator[dict[str, str]]:
|
||||
queue: asyncio.Queue[RunEvent] = asyncio.Queue()
|
||||
|
||||
async def forward(event: RunEvent) -> None:
|
||||
await queue.put(event)
|
||||
|
||||
bus.subscribe(forward)
|
||||
try:
|
||||
replay = bus.replay()
|
||||
watermark = replay[-1].seq if replay else 0
|
||||
for prior in replay:
|
||||
yield {"event": prior.type, "data": prior.model_dump_json()}
|
||||
while True:
|
||||
if actor.state.state in terminal:
|
||||
try:
|
||||
event = await asyncio.wait_for(queue.get(), timeout=0.5)
|
||||
except TimeoutError:
|
||||
return
|
||||
else:
|
||||
event = await queue.get()
|
||||
if event.seq <= watermark:
|
||||
continue
|
||||
yield {"event": event.type, "data": event.model_dump_json()}
|
||||
if event.type in ("run_done", "run_error"):
|
||||
return
|
||||
finally:
|
||||
bus.unsubscribe(forward)
|
||||
|
||||
return EventSourceResponse(event_stream())
|
||||
|
||||
return EventSourceResponse(event_stream())
|
||||
|
||||
@app.get("/api/runs/{run_id}/report")
|
||||
async def run_report(run_id: str) -> dict[str, Any]:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
try:
|
||||
content = await storage.read_run_artifact(run_id, "report.md")
|
||||
except AdapterError:
|
||||
raise HTTPException(status_code=404, detail="report not ready")
|
||||
return {"run_id": run_id, "markdown": content}
|
||||
|
||||
@app.get("/api/runs/{run_id}/ledger")
|
||||
async def run_ledger(run_id: str) -> dict[str, Any]:
|
||||
if not runs.has(run_id):
|
||||
raise HTTPException(status_code=404, detail="run not found")
|
||||
try:
|
||||
content = await storage.read_run_artifact(run_id, "ledger.json")
|
||||
except AdapterError:
|
||||
raise HTTPException(status_code=404, detail="ledger not ready")
|
||||
import json
|
||||
|
||||
data: dict[str, Any] = json.loads(content)
|
||||
return data
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
@@ -10,23 +10,31 @@ class RunEvent(BaseModel):
|
||||
run_id: str
|
||||
at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
data: dict[str, str] = {}
|
||||
seq: int = 0
|
||||
|
||||
|
||||
class EventBus:
|
||||
def __init__(self) -> None:
|
||||
self._subscribers: list[Callable[[RunEvent], Awaitable[None]]] = []
|
||||
self._log: list[RunEvent] = []
|
||||
self._seq = 0
|
||||
|
||||
def subscribe(self, handler: Callable[[RunEvent], Awaitable[None]]) -> None:
|
||||
self._subscribers.append(handler)
|
||||
|
||||
def unsubscribe(self, handler: Callable[[RunEvent], Awaitable[None]]) -> None:
|
||||
if handler in self._subscribers:
|
||||
self._subscribers.remove(handler)
|
||||
|
||||
async def publish(self, event: RunEvent) -> None:
|
||||
self._seq += 1
|
||||
event.seq = self._seq
|
||||
self._log.append(event)
|
||||
for handler in self._subscribers:
|
||||
await handler(event)
|
||||
|
||||
def replay(self) -> list[RunEvent]:
|
||||
return list(self._log)
|
||||
def replay(self, after_seq: int = 0) -> list[RunEvent]:
|
||||
return [e for e in self._log if e.seq > after_seq]
|
||||
|
||||
|
||||
class TraceWriter:
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from researchx.adapters.fakes import CORPUS, FakeModelAdapter, FakeSearchAdapter
|
||||
from researchx.adapters.types import ChatResult
|
||||
from researchx.api.app import create_app
|
||||
|
||||
_PLAN = json.dumps(
|
||||
{"sub_questions": ["verification passes"], "source_universe": ["web"], "outline": ["intro"]}
|
||||
)
|
||||
_WRITER = "# Report\n\nClaim [p-1] and claim [p-2].\n"
|
||||
|
||||
|
||||
def _app(tmp_path: Path):
|
||||
return create_app(
|
||||
storage_dir=tmp_path,
|
||||
model_adapter=FakeModelAdapter(
|
||||
canned=[
|
||||
ChatResult(content=_PLAN, model="fake-planner"),
|
||||
ChatResult(content=_WRITER, model="fake-writer"),
|
||||
]
|
||||
),
|
||||
search_adapter=FakeSearchAdapter(CORPUS),
|
||||
)
|
||||
|
||||
|
||||
async def _client(tmp_path: Path) -> httpx.AsyncClient:
|
||||
return httpx.AsyncClient(
|
||||
transport=httpx.ASGITransport(app=_app(tmp_path)),
|
||||
base_url="http://testserver",
|
||||
)
|
||||
|
||||
|
||||
async def test_config_status(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
response = await client.get("/api/config")
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["model_adapter"] == "FakeModelAdapter"
|
||||
assert body["cost_counters"]["runs_created"] == 0
|
||||
|
||||
|
||||
async def test_methodology_cards(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
response = await client.get("/api/methodologies")
|
||||
assert response.status_code == 200
|
||||
cards = response.json()
|
||||
slugs = [c["slug"] for c in cards]
|
||||
assert "rapid-answer" in slugs and "deep-report" in slugs
|
||||
for card in cards:
|
||||
assert card["eta_minutes"] > 0
|
||||
assert "estimate" in card["cost_estimate"]
|
||||
assert card["why_recommended"]
|
||||
assert card["dials"]["depth"] >= 1
|
||||
|
||||
|
||||
async def test_create_run_rejects_unknown_methodology(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
response = await client.post(
|
||||
"/api/runs", json={"question": "q", "methodology": "nonexistent"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
async def _await_status(client: httpx.AsyncClient, run_id: str, status: str) -> None:
|
||||
for _ in range(200):
|
||||
current = (await client.get(f"/api/runs/{run_id}")).json()
|
||||
if current["status"] == status:
|
||||
return
|
||||
await asyncio.sleep(0.01)
|
||||
raise AssertionError(f"run never reached {status}: {current['status']}")
|
||||
|
||||
|
||||
async def test_run_lifecycle_over_http(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
created = await client.post(
|
||||
"/api/runs", json={"question": "How do engines run?", "methodology": "rapid-answer"}
|
||||
)
|
||||
assert created.status_code == 201
|
||||
run_id = created.json()["id"]
|
||||
|
||||
await _await_status(client, run_id, "awaiting_approval")
|
||||
approved = await client.post(f"/api/runs/{run_id}/approve")
|
||||
assert approved.status_code == 200
|
||||
|
||||
await _await_status(client, run_id, "done")
|
||||
report = await client.get(f"/api/runs/{run_id}/report")
|
||||
assert report.status_code == 200
|
||||
assert "[p-1]" in report.json()["markdown"]
|
||||
|
||||
ledger = await client.get(f"/api/runs/{run_id}/ledger")
|
||||
assert ledger.status_code == 200
|
||||
assert ledger.json()["checklist"]["citations_checked"] >= 1
|
||||
|
||||
config = (await client.get("/api/config")).json()
|
||||
assert config["cost_counters"]["runs_created"] == 1
|
||||
assert config["cost_counters"]["runs_completed"] == 1
|
||||
|
||||
|
||||
async def test_missing_run_404(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
assert (await client.get("/api/runs/zzzz9999")).status_code == 404
|
||||
assert (await client.post("/api/runs/zzzz9999/approve")).status_code == 404
|
||||
assert (await client.get("/api/runs/zzzz9999/events")).status_code == 404
|
||||
|
||||
|
||||
async def test_sse_stream_replays_events(tmp_path: Path) -> None:
|
||||
client = await _client(tmp_path)
|
||||
try:
|
||||
created = await client.post(
|
||||
"/api/runs", json={"question": "q", "methodology": "rapid-answer"}
|
||||
)
|
||||
run_id = created.json()["id"]
|
||||
await _await_status(client, run_id, "awaiting_approval")
|
||||
await client.post(f"/api/runs/{run_id}/approve")
|
||||
await _await_status(client, run_id, "done")
|
||||
|
||||
collected = b""
|
||||
async with client.stream("GET", f"/api/runs/{run_id}/events") as response:
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"].startswith("text/event-stream")
|
||||
async for chunk in response.aiter_bytes():
|
||||
collected += chunk
|
||||
if b"run_done" in collected:
|
||||
break
|
||||
text = collected.decode()
|
||||
assert "event: run_started" in text
|
||||
assert "event: run_done" in text
|
||||
finally:
|
||||
await client.aclose()
|
||||
|
||||
|
||||
async def test_pause_resume_cancel_over_http(tmp_path: Path) -> None:
|
||||
async with await _client(tmp_path) as client:
|
||||
created = await client.post(
|
||||
"/api/runs", json={"question": "q", "methodology": "rapid-answer"}
|
||||
)
|
||||
run_id = created.json()["id"]
|
||||
await _await_status(client, run_id, "awaiting_approval")
|
||||
|
||||
paused = await client.post(f"/api/runs/{run_id}/pause")
|
||||
assert paused.status_code == 200
|
||||
resumed = await client.post(f"/api/runs/{run_id}/resume")
|
||||
assert resumed.status_code == 200
|
||||
|
||||
cancelled = await client.post(f"/api/runs/{run_id}/cancel")
|
||||
assert cancelled.status_code == 200
|
||||
for _ in range(200):
|
||||
status = (await client.get(f"/api/runs/{run_id}")).json()["status"]
|
||||
if status == "cancelled":
|
||||
break
|
||||
await asyncio.sleep(0.01)
|
||||
assert status == "cancelled"
|
||||
Generated
+2
@@ -521,6 +521,7 @@ dependencies = [
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "httpx" },
|
||||
{ name = "mypy" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
@@ -538,6 +539,7 @@ requires-dist = [
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "mypy", specifier = ">=1.11" },
|
||||
{ name = "pytest", specifier = ">=8" },
|
||||
{ name = "pytest-asyncio", specifier = ">=0.23" },
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "ResearchX API",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/config": {
|
||||
"get": {
|
||||
"summary": "Config Status",
|
||||
"operationId": "config_status_api_config_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConfigStatus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/methodologies": {
|
||||
"get": {
|
||||
"summary": "Methodologies",
|
||||
"operationId": "methodologies_api_methodologies_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MethodologyCard"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Response Methodologies Api Methodologies Get"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs": {
|
||||
"post": {
|
||||
"summary": "Create Run",
|
||||
"operationId": "create_run_api_runs_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateRunBody"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}": {
|
||||
"get": {
|
||||
"summary": "Get Run",
|
||||
"operationId": "get_run_api_runs__run_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/approve": {
|
||||
"post": {
|
||||
"summary": "Approve Run",
|
||||
"operationId": "approve_run_api_runs__run_id__approve_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ApproveBody"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Body"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/pause": {
|
||||
"post": {
|
||||
"summary": "Pause Run",
|
||||
"operationId": "pause_run_api_runs__run_id__pause_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/resume": {
|
||||
"post": {
|
||||
"summary": "Resume Run",
|
||||
"operationId": "resume_run_api_runs__run_id__resume_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/cancel": {
|
||||
"post": {
|
||||
"summary": "Cancel Run",
|
||||
"operationId": "cancel_run_api_runs__run_id__cancel_post",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RunSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/events": {
|
||||
"get": {
|
||||
"summary": "Run Events",
|
||||
"operationId": "run_events_api_runs__run_id__events_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/report": {
|
||||
"get": {
|
||||
"summary": "Run Report",
|
||||
"operationId": "run_report_api_runs__run_id__report_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"title": "Response Run Report Api Runs Run Id Report Get"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/runs/{run_id}/ledger": {
|
||||
"get": {
|
||||
"summary": "Run Ledger",
|
||||
"operationId": "run_ledger_api_runs__run_id__ledger_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Run Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"title": "Response Run Ledger Api Runs Run Id Ledger Get"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ApproveBody": {
|
||||
"properties": {
|
||||
"plan": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Plan"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ApproveBody"
|
||||
},
|
||||
"ConfigStatus": {
|
||||
"properties": {
|
||||
"model_adapter": {
|
||||
"type": "string",
|
||||
"title": "Model Adapter"
|
||||
},
|
||||
"search_adapter": {
|
||||
"type": "string",
|
||||
"title": "Search Adapter"
|
||||
},
|
||||
"storage_dir": {
|
||||
"type": "string",
|
||||
"title": "Storage Dir"
|
||||
},
|
||||
"probe": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Probe"
|
||||
},
|
||||
"cost_counters": {
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "object",
|
||||
"title": "Cost Counters"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model_adapter",
|
||||
"search_adapter",
|
||||
"storage_dir",
|
||||
"cost_counters"
|
||||
],
|
||||
"title": "ConfigStatus"
|
||||
},
|
||||
"CreateRunBody": {
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Question"
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"title": "Context",
|
||||
"default": ""
|
||||
},
|
||||
"methodology": {
|
||||
"type": "string",
|
||||
"title": "Methodology"
|
||||
},
|
||||
"dial_overrides": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Dial Overrides"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"question",
|
||||
"methodology"
|
||||
],
|
||||
"title": "CreateRunBody"
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Detail"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "HTTPValidationError"
|
||||
},
|
||||
"MethodologyCard": {
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"title": "Slug"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "Description"
|
||||
},
|
||||
"eta_minutes": {
|
||||
"type": "integer",
|
||||
"title": "Eta Minutes"
|
||||
},
|
||||
"cost_estimate": {
|
||||
"type": "string",
|
||||
"title": "Cost Estimate"
|
||||
},
|
||||
"why_recommended": {
|
||||
"type": "string",
|
||||
"title": "Why Recommended"
|
||||
},
|
||||
"output_shape": {
|
||||
"type": "string",
|
||||
"title": "Output Shape"
|
||||
},
|
||||
"dials": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Dials"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slug",
|
||||
"name",
|
||||
"description",
|
||||
"eta_minutes",
|
||||
"cost_estimate",
|
||||
"why_recommended",
|
||||
"output_shape",
|
||||
"dials"
|
||||
],
|
||||
"title": "MethodologyCard"
|
||||
},
|
||||
"RunSummary": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "Id"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"title": "Status"
|
||||
},
|
||||
"question": {
|
||||
"type": "string",
|
||||
"title": "Question"
|
||||
},
|
||||
"methodology": {
|
||||
"type": "string",
|
||||
"title": "Methodology"
|
||||
},
|
||||
"plan": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Plan"
|
||||
},
|
||||
"started_at": {
|
||||
"type": "string",
|
||||
"title": "Started At"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"status",
|
||||
"question",
|
||||
"methodology",
|
||||
"started_at"
|
||||
],
|
||||
"title": "RunSummary"
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Location"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"title": "Error Type"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"title": "ValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@
|
||||
"eslint-config-next": "15.5.6",
|
||||
"globals": "^17.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"openapi-fetch": "^0.13.8",
|
||||
"openapi-typescript": "^7.13.0",
|
||||
"tailwindcss": "4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.70.1",
|
||||
|
||||
Generated
+288
-123
@@ -168,7 +168,7 @@ importers:
|
||||
dependencies:
|
||||
next:
|
||||
specifier: 15.5.6
|
||||
version: 15.5.6(@babel/core@7.29.7(supports-color@7.2.0))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
version: 15.5.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
react:
|
||||
specifier: 19.2.8
|
||||
version: 19.2.8
|
||||
@@ -178,7 +178,7 @@ importers:
|
||||
devDependencies:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.3.7
|
||||
version: 3.3.7(supports-color@7.2.0)
|
||||
version: 3.3.7(supports-color@10.2.2)
|
||||
'@tailwindcss/postcss':
|
||||
specifier: 4.1.18
|
||||
version: 4.1.18
|
||||
@@ -205,19 +205,25 @@ importers:
|
||||
version: 19.2.3(@types/react@19.2.5)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 4.7.0
|
||||
version: 4.7.0(supports-color@7.2.0)(vite@7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2))
|
||||
version: 4.7.0(supports-color@10.2.2)(vite@7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2))
|
||||
eslint:
|
||||
specifier: ^9.39.5
|
||||
version: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
version: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
eslint-config-next:
|
||||
specifier: 15.5.6
|
||||
version: 15.5.6(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
version: 15.5.6(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
globals:
|
||||
specifier: ^17.0.0
|
||||
version: 17.12.0
|
||||
jsdom:
|
||||
specifier: 26.1.0
|
||||
version: 26.1.0(supports-color@7.2.0)
|
||||
version: 26.1.0(supports-color@10.2.2)
|
||||
openapi-fetch:
|
||||
specifier: ^0.13.8
|
||||
version: 0.13.8
|
||||
openapi-typescript:
|
||||
specifier: ^7.13.0
|
||||
version: 7.13.0(typescript@5.9.3)
|
||||
tailwindcss:
|
||||
specifier: 4.1.18
|
||||
version: 4.1.18
|
||||
@@ -226,10 +232,10 @@ importers:
|
||||
version: 5.9.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.70.1
|
||||
version: 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
version: 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
vitest:
|
||||
specifier: 3.2.7
|
||||
version: 3.2.7(@types/node@24.3.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.30.2)(supports-color@7.2.0)
|
||||
version: 3.2.7(@types/node@24.3.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@10.2.2))(lightningcss@1.30.2)(supports-color@10.2.2)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -841,6 +847,16 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
|
||||
|
||||
'@redocly/config@0.22.0':
|
||||
resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==}
|
||||
|
||||
'@redocly/openapi-core@1.34.20':
|
||||
resolution: {integrity: sha512-ypeBZ/6BKXR9+7/TtbKhbl4UgD7raHhPS12oknlKno2A8+lnFkxIwiE/Aklu6L2cd/ioH+fCWuMxi9/p3EyAPw==}
|
||||
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
@@ -1390,6 +1406,10 @@ packages:
|
||||
ajv@6.15.0:
|
||||
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1482,6 +1502,9 @@ packages:
|
||||
brace-expansion@1.1.21:
|
||||
resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==}
|
||||
|
||||
brace-expansion@2.1.7:
|
||||
resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
|
||||
|
||||
brace-expansion@5.0.12:
|
||||
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -1526,6 +1549,9 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
change-case@5.4.4:
|
||||
resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
|
||||
|
||||
check-error@2.1.3:
|
||||
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -1540,6 +1566,9 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
colorette@1.4.0:
|
||||
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -2006,6 +2035,10 @@ packages:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
index-to-position@1.2.0:
|
||||
resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2134,6 +2167,10 @@ packages:
|
||||
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
|
||||
hasBin: true
|
||||
|
||||
js-levenshtein@1.1.6:
|
||||
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -2164,6 +2201,9 @@ packages:
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
|
||||
@@ -2318,6 +2358,10 @@ packages:
|
||||
minimatch@3.1.5:
|
||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||
|
||||
minimatch@5.1.9:
|
||||
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
@@ -2402,6 +2446,18 @@ packages:
|
||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
openapi-fetch@0.13.8:
|
||||
resolution: {integrity: sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==}
|
||||
|
||||
openapi-typescript-helpers@0.0.15:
|
||||
resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
|
||||
|
||||
openapi-typescript@7.13.0:
|
||||
resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^5.x
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2422,6 +2478,10 @@ packages:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
parse-json@8.3.0:
|
||||
resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
parse5@7.3.0:
|
||||
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
||||
|
||||
@@ -2454,6 +2514,10 @@ packages:
|
||||
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pluralize@8.0.0:
|
||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
possible-typed-array-names@1.1.0:
|
||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2515,6 +2579,10 @@ packages:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2684,6 +2752,10 @@ packages:
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
supports-color@10.2.2:
|
||||
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2759,6 +2831,10 @@ packages:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
type-fest@4.41.0:
|
||||
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2803,6 +2879,9 @@ packages:
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
uri-js-replace@1.0.1:
|
||||
resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
|
||||
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
@@ -2952,6 +3031,13 @@ packages:
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
yaml-ast-parser@0.0.43:
|
||||
resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2978,20 +3064,20 @@ snapshots:
|
||||
|
||||
'@babel/compat-data@7.29.7': {}
|
||||
|
||||
'@babel/core@7.29.7(supports-color@7.2.0)':
|
||||
'@babel/core@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/generator': 7.29.8
|
||||
'@babel/helper-compilation-targets': 7.29.7
|
||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@babel/helpers': 7.29.7
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/traverse': 7.29.8(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -3016,19 +3102,19 @@ snapshots:
|
||||
|
||||
'@babel/helper-globals@7.29.7': {}
|
||||
|
||||
'@babel/helper-module-imports@7.29.7(supports-color@7.2.0)':
|
||||
'@babel/helper-module-imports@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/traverse': 7.29.8(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)':
|
||||
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
'@babel/traverse': 7.29.8(supports-color@7.2.0)
|
||||
'@babel/traverse': 7.29.8(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3049,14 +3135,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.29.8
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||
'@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))':
|
||||
'@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
|
||||
'@babel/runtime@7.29.7': {}
|
||||
@@ -3067,7 +3153,7 @@ snapshots:
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/types': 7.29.8
|
||||
|
||||
'@babel/traverse@7.29.8(supports-color@7.2.0)':
|
||||
'@babel/traverse@7.29.8(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/generator': 7.29.8
|
||||
@@ -3075,7 +3161,7 @@ snapshots:
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/types': 7.29.8
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3203,17 +3289,17 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.28.2':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))':
|
||||
'@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))':
|
||||
dependencies:
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
|
||||
'@eslint/config-array@0.21.2(supports-color@7.2.0)':
|
||||
'@eslint/config-array@0.21.2(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@eslint/object-schema': 2.1.7
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
minimatch: 3.1.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3226,10 +3312,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/eslintrc@3.3.7(supports-color@7.2.0)':
|
||||
'@eslint/eslintrc@3.3.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
ajv: 6.15.0
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
espree: 10.4.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
@@ -3435,6 +3521,29 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
uri-js-replace: 1.0.1
|
||||
|
||||
'@redocly/config@0.22.0': {}
|
||||
|
||||
'@redocly/openapi-core@1.34.20(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@redocly/ajv': 8.11.2
|
||||
'@redocly/config': 0.22.0
|
||||
colorette: 1.4.0
|
||||
https-proxy-agent: 7.0.6(supports-color@10.2.2)
|
||||
js-levenshtein: 1.1.6
|
||||
js-yaml: 4.3.2
|
||||
minimatch: 5.1.9
|
||||
pluralize: 8.0.0
|
||||
yaml-ast-parser: 0.0.43
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.63.5':
|
||||
@@ -3677,15 +3786,15 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.70.1
|
||||
'@typescript-eslint/type-utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/type-utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.70.1
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
ignore: 7.0.10
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||
@@ -3693,23 +3802,23 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.70.1
|
||||
'@typescript-eslint/types': 8.70.1
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.70.1
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.70.1(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/project-service@8.70.1(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.70.1
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3723,13 +3832,13 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/type-utils@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.70.1
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
ts-api-utils: 2.5.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -3737,13 +3846,13 @@ snapshots:
|
||||
|
||||
'@typescript-eslint/types@8.70.1': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.70.1(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/typescript-estree@8.70.1(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.70.1(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/project-service': 8.70.1(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.70.1
|
||||
'@typescript-eslint/visitor-keys': 8.70.1
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
minimatch: 10.2.6
|
||||
semver: 7.8.5
|
||||
tinyglobby: 0.2.17
|
||||
@@ -3752,13 +3861,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/utils@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))
|
||||
'@typescript-eslint/scope-manager': 8.70.1
|
||||
'@typescript-eslint/types': 8.70.1
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3838,11 +3947,11 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.12.2':
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-react@4.7.0(supports-color@7.2.0)(vite@7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2))':
|
||||
'@vitejs/plugin-react@4.7.0(supports-color@10.2.2)(vite@7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))
|
||||
'@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
'@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
@@ -3907,6 +4016,8 @@ snapshots:
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
@@ -4015,6 +4126,10 @@ snapshots:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@2.1.7:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
brace-expansion@5.0.12:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
@@ -4067,6 +4182,8 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
change-case@5.4.4: {}
|
||||
|
||||
check-error@2.1.3: {}
|
||||
|
||||
client-only@0.0.1: {}
|
||||
@@ -4077,6 +4194,8 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
colorette@1.4.0: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
@@ -4121,17 +4240,17 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
debug@3.2.7(supports-color@7.2.0):
|
||||
debug@3.2.7(supports-color@10.2.2):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 7.2.0
|
||||
supports-color: 10.2.2
|
||||
|
||||
debug@4.4.3(supports-color@7.2.0):
|
||||
debug@4.4.3(supports-color@10.2.2):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 7.2.0
|
||||
supports-color: 10.2.2
|
||||
|
||||
decimal.js@10.6.0: {}
|
||||
|
||||
@@ -4326,19 +4445,19 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-config-next@15.5.6(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3):
|
||||
eslint-config-next@15.5.6(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.5.6
|
||||
'@rushstack/eslint-patch': 1.16.1
|
||||
'@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))
|
||||
'@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@10.2.2)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -4346,52 +4465,52 @@ snapshots:
|
||||
- eslint-plugin-import-x
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-node@0.3.10(supports-color@7.2.0):
|
||||
eslint-import-resolver-node@0.3.10(supports-color@10.2.2):
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@7.2.0)
|
||||
debug: 3.2.7(supports-color@10.2.2)
|
||||
is-core-module: 2.17.0
|
||||
resolve: 2.0.0-next.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
get-tsconfig: 4.14.3
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.17
|
||||
unrs-resolver: 1.12.2
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.14.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
|
||||
eslint-module-utils@2.14.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@10.2.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2):
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@7.2.0)
|
||||
debug: 3.2.7(supports-color@10.2.2)
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@10.2.2)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.2.0
|
||||
array.prototype.findlastindex: 1.2.6
|
||||
array.prototype.flat: 1.3.3
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7(supports-color@7.2.0)
|
||||
debug: 3.2.7(supports-color@10.2.2)
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
|
||||
eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
eslint-import-resolver-node: 0.3.10(supports-color@10.2.2)
|
||||
eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@10.2.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
hasown: 2.0.4
|
||||
is-core-module: 2.17.0
|
||||
is-glob: 4.0.3
|
||||
@@ -4403,13 +4522,13 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.10
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)):
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)):
|
||||
dependencies:
|
||||
aria-query: 5.3.2
|
||||
array-includes: 3.2.0
|
||||
@@ -4419,7 +4538,7 @@ snapshots:
|
||||
axobject-query: 4.1.0
|
||||
damerau-levenshtein: 1.0.8
|
||||
emoji-regex: 9.2.2
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
hasown: 2.0.4
|
||||
jsx-ast-utils: 3.3.5
|
||||
language-tags: 1.0.9
|
||||
@@ -4428,11 +4547,11 @@ snapshots:
|
||||
safe-regex-test: 1.1.0
|
||||
string.prototype.includes: 2.0.1
|
||||
|
||||
eslint-plugin-react-hooks@5.2.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)):
|
||||
eslint-plugin-react-hooks@5.2.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)):
|
||||
dependencies:
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)):
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)):
|
||||
dependencies:
|
||||
array-includes: 3.2.0
|
||||
array.prototype.findlast: 1.2.5
|
||||
@@ -4440,7 +4559,7 @@ snapshots:
|
||||
array.prototype.tosorted: 1.1.4
|
||||
doctrine: 2.1.0
|
||||
es-iterator-helpers: 1.4.0
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
estraverse: 5.3.0
|
||||
hasown: 2.0.4
|
||||
jsx-ast-utils: 3.3.5
|
||||
@@ -4465,14 +4584,14 @@ snapshots:
|
||||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0):
|
||||
eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))
|
||||
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.2(supports-color@7.2.0)
|
||||
'@eslint/config-array': 0.21.2(supports-color@10.2.2)
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.7(supports-color@7.2.0)
|
||||
'@eslint/eslintrc': 3.3.7(supports-color@10.2.2)
|
||||
'@eslint/js': 9.39.5
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.8
|
||||
@@ -4482,7 +4601,7 @@ snapshots:
|
||||
ajv: 6.15.0
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
@@ -4674,17 +4793,17 @@ snapshots:
|
||||
dependencies:
|
||||
whatwg-encoding: 3.1.1
|
||||
|
||||
http-proxy-agent@7.0.2(supports-color@7.2.0):
|
||||
http-proxy-agent@7.0.2(supports-color@10.2.2):
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6(supports-color@7.2.0):
|
||||
https-proxy-agent@7.0.6(supports-color@10.2.2):
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4705,6 +4824,8 @@ snapshots:
|
||||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
index-to-position@1.2.0: {}
|
||||
|
||||
internal-slot@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -4844,6 +4965,8 @@ snapshots:
|
||||
|
||||
jiti@2.7.0: {}
|
||||
|
||||
js-levenshtein@1.1.6: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -4852,14 +4975,14 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
jsdom@26.1.0(supports-color@7.2.0):
|
||||
jsdom@26.1.0(supports-color@10.2.2):
|
||||
dependencies:
|
||||
cssstyle: 4.6.0
|
||||
data-urls: 5.0.0
|
||||
decimal.js: 10.6.0
|
||||
html-encoding-sniffer: 4.0.0
|
||||
http-proxy-agent: 7.0.2(supports-color@7.2.0)
|
||||
https-proxy-agent: 7.0.6(supports-color@7.2.0)
|
||||
http-proxy-agent: 7.0.2(supports-color@10.2.2)
|
||||
https-proxy-agent: 7.0.6(supports-color@10.2.2)
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
nwsapi: 2.2.28
|
||||
parse5: 7.3.0
|
||||
@@ -4885,6 +5008,8 @@ snapshots:
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
||||
json5@1.0.2:
|
||||
@@ -5007,6 +5132,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.21
|
||||
|
||||
minimatch@5.1.9:
|
||||
dependencies:
|
||||
brace-expansion: 2.1.7
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
@@ -5017,7 +5146,7 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
next@15.5.6(@babel/core@7.29.7(supports-color@7.2.0))(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
|
||||
next@15.5.6(@babel/core@7.29.7(supports-color@10.2.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
|
||||
dependencies:
|
||||
'@next/env': 15.5.6
|
||||
'@swc/helpers': 0.5.15
|
||||
@@ -5025,7 +5154,7 @@ snapshots:
|
||||
postcss: 8.4.31
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.2.8)
|
||||
styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.5.6
|
||||
'@next/swc-darwin-x64': 15.5.6
|
||||
@@ -5093,6 +5222,22 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
openapi-fetch@0.13.8:
|
||||
dependencies:
|
||||
openapi-typescript-helpers: 0.0.15
|
||||
|
||||
openapi-typescript-helpers@0.0.15: {}
|
||||
|
||||
openapi-typescript@7.13.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@redocly/openapi-core': 1.34.20(supports-color@10.2.2)
|
||||
ansi-colors: 4.1.3
|
||||
change-case: 5.4.4
|
||||
parse-json: 8.3.0
|
||||
supports-color: 10.2.2
|
||||
typescript: 5.9.3
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -5121,6 +5266,12 @@ snapshots:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
parse-json@8.3.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
index-to-position: 1.2.0
|
||||
type-fest: 4.41.0
|
||||
|
||||
parse5@7.3.0:
|
||||
dependencies:
|
||||
entities: 6.0.1
|
||||
@@ -5141,6 +5292,8 @@ snapshots:
|
||||
|
||||
picomatch@4.0.7: {}
|
||||
|
||||
pluralize@8.0.0: {}
|
||||
|
||||
possible-typed-array-names@1.1.0: {}
|
||||
|
||||
postcss@8.4.31:
|
||||
@@ -5211,6 +5364,8 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@@ -5461,12 +5616,14 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 9.0.1
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.2.8):
|
||||
styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.2.8):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.29.7(supports-color@7.2.0)
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
|
||||
supports-color@10.2.2: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
@@ -5530,6 +5687,8 @@ snapshots:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -5562,13 +5721,13 @@ snapshots:
|
||||
possible-typed-array-names: 1.1.0
|
||||
reflect.getprototypeof: 1.0.10
|
||||
|
||||
typescript-eslint@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3):
|
||||
typescript-eslint@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||
'@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.70.1(supports-color@10.2.2)(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)
|
||||
eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -5617,14 +5776,16 @@ snapshots:
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
uri-js-replace@1.0.1: {}
|
||||
|
||||
uri-js@4.4.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
vite-node@3.2.4(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)(supports-color@7.2.0):
|
||||
vite-node@3.2.4(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)(supports-color@10.2.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)
|
||||
@@ -5656,7 +5817,7 @@ snapshots:
|
||||
jiti: 2.7.0
|
||||
lightningcss: 1.30.2
|
||||
|
||||
vitest@3.2.7(@types/node@24.3.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.30.2)(supports-color@7.2.0):
|
||||
vitest@3.2.7(@types/node@24.3.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@10.2.2))(lightningcss@1.30.2)(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.7
|
||||
@@ -5667,7 +5828,7 @@ snapshots:
|
||||
'@vitest/spy': 3.2.7
|
||||
'@vitest/utils': 3.2.7
|
||||
chai: 5.3.3
|
||||
debug: 4.4.3(supports-color@7.2.0)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
expect-type: 1.4.0
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
@@ -5679,11 +5840,11 @@ snapshots:
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 7.3.6(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)
|
||||
vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)(supports-color@7.2.0)
|
||||
vite-node: 3.2.4(@types/node@24.3.1)(jiti@2.7.0)(lightningcss@1.30.2)(supports-color@10.2.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.3.1
|
||||
jsdom: 26.1.0(supports-color@7.2.0)
|
||||
jsdom: 26.1.0(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
@@ -5775,4 +5936,8 @@ snapshots:
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml-ast-parser@0.0.43: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
UserProfile,
|
||||
} from "@/lib/types";
|
||||
import { MockApiClient } from "@/lib/api/mock/mock-client";
|
||||
import { GeneratedApiClient } from "@/lib/api/live-client";
|
||||
|
||||
export type { Report, ReportSection } from "@/lib/types";
|
||||
export type {
|
||||
@@ -66,7 +67,8 @@ export interface CreateRunInput {
|
||||
|
||||
/**
|
||||
* Subscription handle for the live trace stream (EventSource-shaped).
|
||||
* v0.2 backs this with SSE; pause/resume stay client-side concerns.
|
||||
* v0.2: backed by SSE in live mode; client-side pause/resume kept
|
||||
* (server-side pause/resume added via the P3 extension below).
|
||||
*/
|
||||
export interface RunEventSubscription {
|
||||
/** Stop the stream and release its timers (idempotent). */
|
||||
@@ -77,6 +79,18 @@ export interface RunEventSubscription {
|
||||
resume(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.2 P3 extension (grill G-003 — the single planned interface
|
||||
* addition): server-side pause/resume of a running engine across
|
||||
* restarts. Mock mode ignores it; live mode hits the backend.
|
||||
*/
|
||||
export interface RunLifecycle {
|
||||
/** Pause the engine server-side (durable across restarts). */
|
||||
pauseRun(id: string): Promise<ResearchRun>;
|
||||
/** Resume a server-side-paused run. */
|
||||
resumeRun(id: string): Promise<ResearchRun>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The backend-swappable contract. Every screen talks to this — never
|
||||
* to a concrete client, never to the database, never to the network.
|
||||
@@ -181,7 +195,29 @@ export interface ApiClient {
|
||||
|
||||
/**
|
||||
* The app-wide singleton. v0.1: MockApiClient (fixture data + scripted
|
||||
* event replay, zero API keys). v0.2 P3: rebind to the OpenAPI-generated
|
||||
* client — this line is the only thing that changes.
|
||||
* event replay, zero API keys). v0.2 P3 (REQ-26/REQ-54): the binding
|
||||
* switches to the OpenAPI-generated client in live mode — selected by
|
||||
* NEXT_PUBLIC_RESEARCHX_API_MODE=live (mock stays the default). Live mode covers
|
||||
* the core research loop only (G-002); other surfaces stay mock-backed.
|
||||
*/
|
||||
export const api: ApiClient = new MockApiClient();
|
||||
class MockApiClientWithLifecycle extends MockApiClient implements RunLifecycle {
|
||||
async pauseRun(id: string): Promise<ResearchRun> {
|
||||
return this.getRun(id);
|
||||
}
|
||||
async resumeRun(id: string): Promise<ResearchRun> {
|
||||
return this.getRun(id);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBinding(): ApiClient & RunLifecycle {
|
||||
const mode =
|
||||
process.env.NEXT_PUBLIC_RESEARCHX_API_MODE ??
|
||||
process.env.RESEARCHX_API_MODE ??
|
||||
"mock";
|
||||
if (mode === "live") {
|
||||
return new GeneratedApiClient() as unknown as ApiClient & RunLifecycle;
|
||||
}
|
||||
return new MockApiClientWithLifecycle();
|
||||
}
|
||||
|
||||
export const api: ApiClient & RunLifecycle = resolveBinding();
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/api/config": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Config Status */
|
||||
get: operations["config_status_api_config_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/methodologies": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Methodologies */
|
||||
get: operations["methodologies_api_methodologies_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Create Run */
|
||||
post: operations["create_run_api_runs_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get Run */
|
||||
get: operations["get_run_api_runs__run_id__get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/approve": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Approve Run */
|
||||
post: operations["approve_run_api_runs__run_id__approve_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/pause": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Pause Run */
|
||||
post: operations["pause_run_api_runs__run_id__pause_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/resume": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Resume Run */
|
||||
post: operations["resume_run_api_runs__run_id__resume_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/cancel": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Cancel Run */
|
||||
post: operations["cancel_run_api_runs__run_id__cancel_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/events": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Run Events */
|
||||
get: operations["run_events_api_runs__run_id__events_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/report": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Run Report */
|
||||
get: operations["run_report_api_runs__run_id__report_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/runs/{run_id}/ledger": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Run Ledger */
|
||||
get: operations["run_ledger_api_runs__run_id__ledger_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** ApproveBody */
|
||||
ApproveBody: {
|
||||
/** Plan */
|
||||
plan?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
/** ConfigStatus */
|
||||
ConfigStatus: {
|
||||
/** Model Adapter */
|
||||
model_adapter: string;
|
||||
/** Search Adapter */
|
||||
search_adapter: string;
|
||||
/** Storage Dir */
|
||||
storage_dir: string;
|
||||
/** Probe */
|
||||
probe?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Cost Counters */
|
||||
cost_counters: {
|
||||
[key: string]: number;
|
||||
};
|
||||
};
|
||||
/** CreateRunBody */
|
||||
CreateRunBody: {
|
||||
/** Question */
|
||||
question: string;
|
||||
/**
|
||||
* Context
|
||||
* @default
|
||||
*/
|
||||
context: string;
|
||||
/** Methodology */
|
||||
methodology: string;
|
||||
/** Dial Overrides */
|
||||
dial_overrides?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
/** HTTPValidationError */
|
||||
HTTPValidationError: {
|
||||
/** Detail */
|
||||
detail?: components["schemas"]["ValidationError"][];
|
||||
};
|
||||
/** MethodologyCard */
|
||||
MethodologyCard: {
|
||||
/** Slug */
|
||||
slug: string;
|
||||
/** Name */
|
||||
name: string;
|
||||
/** Description */
|
||||
description: string;
|
||||
/** Eta Minutes */
|
||||
eta_minutes: number;
|
||||
/** Cost Estimate */
|
||||
cost_estimate: string;
|
||||
/** Why Recommended */
|
||||
why_recommended: string;
|
||||
/** Output Shape */
|
||||
output_shape: string;
|
||||
/** Dials */
|
||||
dials: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
/** RunSummary */
|
||||
RunSummary: {
|
||||
/** Id */
|
||||
id: string;
|
||||
/** Status */
|
||||
status: string;
|
||||
/** Question */
|
||||
question: string;
|
||||
/** Methodology */
|
||||
methodology: string;
|
||||
/** Plan */
|
||||
plan?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Started At */
|
||||
started_at: string;
|
||||
};
|
||||
/** ValidationError */
|
||||
ValidationError: {
|
||||
/** Location */
|
||||
loc: (string | number)[];
|
||||
/** Message */
|
||||
msg: string;
|
||||
/** Error Type */
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
config_status_api_config_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ConfigStatus"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
methodologies_api_methodologies_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["MethodologyCard"][];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
create_run_api_runs_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["CreateRunBody"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
get_run_api_runs__run_id__get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
approve_run_api_runs__run_id__approve_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApproveBody"] | null;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
pause_run_api_runs__run_id__pause_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
resume_run_api_runs__run_id__resume_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cancel_run_api_runs__run_id__cancel_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RunSummary"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
run_events_api_runs__run_id__events_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
run_report_api_runs__run_id__report_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
run_ledger_api_runs__run_id__ledger_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
run_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Boundary tests for the v0.2 P3 client swap (REQ-26, G-003).
|
||||
*
|
||||
* Live mode is contract-tested against a stubbed global fetch — no
|
||||
* network, no keys. Mock mode stays pinned by the existing suite.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { GeneratedApiClient } from "@/lib/api/live-client";
|
||||
import type { BackendRun } from "@/lib/api/test-helpers";
|
||||
|
||||
type RouteHandler = (url: string, init?: RequestInit) => { status: number; body: unknown };
|
||||
|
||||
function stubRoutes(routes: Record<string, RouteHandler>): typeof globalThis.fetch {
|
||||
return (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = input instanceof Request ? input.url : String(input);
|
||||
for (const [prefix, handler] of Object.entries(routes)) {
|
||||
if (url.startsWith(prefix)) {
|
||||
const { status, body } = handler(url, init);
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
return new Response(JSON.stringify({ detail: "not found" }), { status: 404 });
|
||||
}) as typeof globalThis.fetch;
|
||||
}
|
||||
|
||||
const backendRun: BackendRun = {
|
||||
id: "abcd2345",
|
||||
status: "awaiting_approval",
|
||||
question: "How do engines run?",
|
||||
methodology: "rapid-answer",
|
||||
plan: {
|
||||
sub_questions: ["verification passes"],
|
||||
source_universe: ["web"],
|
||||
outline: ["intro"],
|
||||
},
|
||||
started_at: "2026-09-27T12:00:00Z",
|
||||
};
|
||||
|
||||
describe("GeneratedApiClient (live binding)", () => {
|
||||
it("maps backend methodology cards to the frontend Methodology shape", async () => {
|
||||
const routes = {
|
||||
"http://127.0.0.1:8000/api/methodologies": (): { status: number; body: unknown } => ({
|
||||
status: 200,
|
||||
body: [
|
||||
{
|
||||
slug: "rapid-answer",
|
||||
name: "Rapid Answer",
|
||||
description: "One pass, one memo. Fastest path.",
|
||||
eta_minutes: 2,
|
||||
cost_estimate: "~5k tokens (estimate)",
|
||||
why_recommended: "Fastest path to a confident cited answer.",
|
||||
output_shape: "memo",
|
||||
dials: { depth: 1, breadth: 1 },
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
const client = new GeneratedApiClient(stubRoutes(routes));
|
||||
const cards = await client.getMethodologies();
|
||||
expect(cards).toHaveLength(1);
|
||||
expect(cards[0]?.slug).toBe("rapid-answer");
|
||||
expect(cards[0]?.etaMinutes).toBe(2);
|
||||
expect(cards[0]?.costEstimate).toContain("estimate");
|
||||
expect(cards[0]?.defaultDials).toEqual({ depth: 1, breadth: 1 });
|
||||
});
|
||||
|
||||
it("creates runs through the backend contract", async () => {
|
||||
const routes = {
|
||||
"http://127.0.0.1:8000/api/runs": (url: string, init?: RequestInit) => {
|
||||
const method =
|
||||
init?.method ?? (url.endsWith("/api/runs") ? "POST" : undefined);
|
||||
return {
|
||||
status: 201,
|
||||
body: method === "POST" ? backendRun : { detail: "wrong method" },
|
||||
};
|
||||
},
|
||||
};
|
||||
const client = new GeneratedApiClient(stubRoutes(routes));
|
||||
const run = await client.createRun({
|
||||
question: "How do engines run?",
|
||||
methodologySlug: "rapid-answer",
|
||||
});
|
||||
expect(run.id).toBe("abcd2345");
|
||||
expect(run.status).toBe("awaiting_approval");
|
||||
expect(run.plan.subQuestions).toEqual(["verification passes"]);
|
||||
expect(run.plan.methodologySlug).toBe("rapid-answer");
|
||||
});
|
||||
|
||||
it("approves, cancels, pauses, resumes via lifecycle routes", async () => {
|
||||
const routes = {
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/approve": () => ({
|
||||
status: 200,
|
||||
body: { ...backendRun, status: "searching" },
|
||||
}),
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/cancel": () => ({
|
||||
status: 200,
|
||||
body: { ...backendRun, status: "cancelled" },
|
||||
}),
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/pause": () => ({
|
||||
status: 200,
|
||||
body: { ...backendRun, status: "searching" },
|
||||
}),
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/resume": () => ({
|
||||
status: 200,
|
||||
body: { ...backendRun, status: "verifying" },
|
||||
}),
|
||||
};
|
||||
const client = new GeneratedApiClient(stubRoutes(routes));
|
||||
expect((await client.approvePlan("abcd2345")).status).toBe("searching");
|
||||
expect((await client.cancelRun("abcd2345")).status).toBe("cancelled");
|
||||
expect((await client.pauseRun("abcd2345")).status).toBe("searching");
|
||||
expect((await client.resumeRun("abcd2345")).status).toBe("verifying");
|
||||
});
|
||||
|
||||
it("maps the ledger to frontend citations with health + tier", async () => {
|
||||
const routes = {
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/ledger": () => ({
|
||||
status: 200,
|
||||
body: {
|
||||
run_id: "abcd2345",
|
||||
generated_at: "2026-09-27T12:01:00Z",
|
||||
entries: [
|
||||
{
|
||||
url: "https://example.org/a",
|
||||
visited_at: "2026-09-27T12:00:00Z",
|
||||
passages: ["p-1", "p-2"],
|
||||
claims_supported: 2,
|
||||
health: "live",
|
||||
authority_tier: "official",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
const client = new GeneratedApiClient(stubRoutes(routes));
|
||||
const ledger = await client.getLedger("abcd2345");
|
||||
expect(ledger.runId).toBe("abcd2345");
|
||||
expect(ledger.citations[0]?.verification).toBe("live");
|
||||
expect(ledger.citations[0]?.authorityTier).toBe(1);
|
||||
});
|
||||
|
||||
it("fetches the report markdown", async () => {
|
||||
const routes = {
|
||||
"http://127.0.0.1:8000/api/runs/abcd2345/report": () => ({
|
||||
status: 200,
|
||||
body: { run_id: "abcd2345", markdown: "# Report\n\nClaim [p-1]." },
|
||||
}),
|
||||
};
|
||||
const client = new GeneratedApiClient(stubRoutes(routes));
|
||||
const report = await client.getReport("abcd2345");
|
||||
expect(report.sections[0]?.body).toContain("[p-1]");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* GeneratedApiClient — the live-mode ApiClient binding (v0.2 P3, REQ-26/REQ-54).
|
||||
*
|
||||
* Core research loop methods (methodologies, runs, events, report,
|
||||
* ledger, export) hit the FastAPI backend through openapi-fetch,
|
||||
* typed by the generated schema. Per G-002, the remaining prototype
|
||||
* surfaces (library, settings, suggestions, refine, graph, inbox,
|
||||
* profile) stay mock-backed until v0.3+ — this class delegates those
|
||||
* to the MockApiClient rather than reimplementing them.
|
||||
*
|
||||
* Components never import this class — only client.ts (the boundary)
|
||||
* does, selected by RESEARCHX_API_MODE=live (mock is the default).
|
||||
*/
|
||||
|
||||
import createClient from "openapi-fetch";
|
||||
|
||||
import type { paths } from "@/lib/api/generated/schema";
|
||||
import type {
|
||||
CitationLedger,
|
||||
Methodology,
|
||||
Report,
|
||||
ResearchRun,
|
||||
TraceEvent,
|
||||
} from "@/lib/types";
|
||||
import type {
|
||||
CreateRunInput,
|
||||
RunEventSubscription,
|
||||
} from "@/lib/api/client";
|
||||
import { MockApiClient } from "@/lib/api/mock/mock-client";
|
||||
|
||||
function baseUrl(): string {
|
||||
return (
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://127.0.0.1:8000"
|
||||
);
|
||||
}
|
||||
|
||||
type BackendRun = {
|
||||
id: string;
|
||||
status: string;
|
||||
question: string;
|
||||
methodology: string;
|
||||
plan: {
|
||||
sub_questions: string[];
|
||||
source_universe: string[];
|
||||
outline: string[];
|
||||
} | null;
|
||||
started_at: string;
|
||||
};
|
||||
|
||||
type BackendCard = {
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
eta_minutes: number;
|
||||
cost_estimate: string;
|
||||
why_recommended: string;
|
||||
output_shape: string;
|
||||
dials: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export class GeneratedApiClient extends MockApiClient {
|
||||
#client: ReturnType<typeof createClient<paths>>;
|
||||
|
||||
constructor(fetchImpl?: typeof globalThis.fetch) {
|
||||
super();
|
||||
this.#client = createClient<paths>({
|
||||
baseUrl: baseUrl(),
|
||||
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
override async getMethodologies(): Promise<Methodology[]> {
|
||||
const { data, error } = await this.#client.GET("/api/methodologies");
|
||||
if (error || !data) {
|
||||
throw new Error("live: failed to load methodologies");
|
||||
}
|
||||
return (data as BackendCard[]).map((card, index) => ({
|
||||
slug: card.slug,
|
||||
name: card.name,
|
||||
tagline: card.description.split(".")[0] ?? card.description,
|
||||
description: card.description,
|
||||
etaMinutes: card.eta_minutes,
|
||||
costEstimate: card.cost_estimate,
|
||||
whyRecommended: card.why_recommended,
|
||||
recommended: index === 0,
|
||||
verification: card.slug === "deep-report" ? "full" : "standard",
|
||||
dialSummary: Object.entries(card.dials).map(
|
||||
([dial, value]) => `${dial}: ${String(value)}`,
|
||||
),
|
||||
defaultDials: card.dials as unknown as Methodology["defaultDials"],
|
||||
badge: card.slug === "rapid-answer" ? "Fastest" : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
#toRun(run: BackendRun): ResearchRun {
|
||||
const plan = run.plan;
|
||||
return {
|
||||
id: run.id,
|
||||
status: run.status as ResearchRun["status"],
|
||||
plan: {
|
||||
id: run.id,
|
||||
question: run.question,
|
||||
methodologySlug: run.methodology,
|
||||
subQuestions: plan?.sub_questions ?? [],
|
||||
sourceUniverse: plan?.source_universe ?? [],
|
||||
outline: plan?.outline ?? [],
|
||||
},
|
||||
startedAt: run.started_at,
|
||||
};
|
||||
}
|
||||
|
||||
override async createRun(input: CreateRunInput): Promise<ResearchRun> {
|
||||
const overrides: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(input.dialOverrides ?? {})) {
|
||||
const snake = key.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`);
|
||||
overrides[snake] = value;
|
||||
}
|
||||
const { data, error } = await this.#client.POST("/api/runs", {
|
||||
body: {
|
||||
question: input.question,
|
||||
context: input.context ?? "",
|
||||
methodology: input.methodologySlug,
|
||||
dial_overrides: overrides,
|
||||
},
|
||||
});
|
||||
if (error || !data) {
|
||||
throw new Error("live: failed to create run");
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
override async getRun(id: string): Promise<ResearchRun> {
|
||||
const { data, error } = await this.#client.GET("/api/runs/{run_id}", {
|
||||
params: { path: { run_id: id } },
|
||||
});
|
||||
if (error || !data) {
|
||||
throw new Error(`live: failed to load run ${id}`);
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
override async approvePlan(id: string): Promise<ResearchRun> {
|
||||
const { data, error } = await this.#client.POST(
|
||||
"/api/runs/{run_id}/approve",
|
||||
{ params: { path: { run_id: id } }, body: {} },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: failed to approve run ${id}`);
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
override async cancelRun(id: string): Promise<ResearchRun> {
|
||||
const { data, error } = await this.#client.POST(
|
||||
"/api/runs/{run_id}/cancel",
|
||||
{ params: { path: { run_id: id } } },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: failed to cancel run ${id}`);
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
async pauseRun(id: string): Promise<ResearchRun> {
|
||||
const { data, error } = await this.#client.POST(
|
||||
"/api/runs/{run_id}/pause",
|
||||
{ params: { path: { run_id: id } } },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: failed to pause run ${id}`);
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
async resumeRun(id: string): Promise<ResearchRun> {
|
||||
const { data, error } = await this.#client.POST(
|
||||
"/api/runs/{run_id}/resume",
|
||||
{ params: { path: { run_id: id } } },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: failed to resume run ${id}`);
|
||||
}
|
||||
return this.#toRun(data as BackendRun);
|
||||
}
|
||||
|
||||
override getRunEvents(
|
||||
id: string,
|
||||
onEvent: (event: TraceEvent) => void,
|
||||
onDone?: () => void,
|
||||
): RunEventSubscription {
|
||||
const source = new EventSource(
|
||||
`${baseUrl()}/api/runs/${encodeURIComponent(id)}/events`,
|
||||
);
|
||||
let paused = false;
|
||||
let buffer: TraceEvent[] = [];
|
||||
let counter = 0;
|
||||
|
||||
const deliver = (event: TraceEvent) => {
|
||||
if (paused) {
|
||||
buffer.push(event);
|
||||
} else {
|
||||
onEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
const mapType = (type: string): TraceEvent["type"] => {
|
||||
if (type === "query_fired") return "query";
|
||||
if (type === "source_visited") return "source_visited";
|
||||
if (type === "passage_extracted") return "passage_extracted";
|
||||
if (type === "state_changed") return "status";
|
||||
if (type === "run_done" || type === "run_started") return "status";
|
||||
if (type === "run_error") return "status";
|
||||
return "status";
|
||||
};
|
||||
|
||||
const handle = (backendEvent: { type: string; data: unknown }) => {
|
||||
let detail = "";
|
||||
let message = backendEvent.type;
|
||||
if (typeof backendEvent.data === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(backendEvent.data) as {
|
||||
type: string;
|
||||
data: Record<string, string>;
|
||||
};
|
||||
message =
|
||||
parsed.data?.message ??
|
||||
parsed.data?.query ??
|
||||
parsed.data?.url ??
|
||||
parsed.data?.state ??
|
||||
backendEvent.type;
|
||||
detail = JSON.stringify(parsed.data);
|
||||
} catch {
|
||||
detail = backendEvent.data;
|
||||
}
|
||||
}
|
||||
counter += 1;
|
||||
deliver({
|
||||
id: `live-${counter}`,
|
||||
ts: new Date().toISOString(),
|
||||
type: mapType(backendEvent.type),
|
||||
message,
|
||||
detail,
|
||||
});
|
||||
if (backendEvent.type === "run_done" || backendEvent.type === "run_error") {
|
||||
source.close();
|
||||
onDone?.();
|
||||
}
|
||||
};
|
||||
|
||||
source.onmessage = (raw) => {
|
||||
handle({ type: "message", data: raw.data });
|
||||
};
|
||||
for (const type of [
|
||||
"run_started",
|
||||
"query_fired",
|
||||
"source_found",
|
||||
"source_visited",
|
||||
"passage_extracted",
|
||||
"source_refused",
|
||||
"state_changed",
|
||||
"probe_attached",
|
||||
"run_done",
|
||||
"run_error",
|
||||
]) {
|
||||
source.addEventListener(type, (raw) => {
|
||||
handle({ type, data: (raw as MessageEvent).data });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
cancel() {
|
||||
source.close();
|
||||
},
|
||||
pause() {
|
||||
paused = true;
|
||||
},
|
||||
resume() {
|
||||
paused = false;
|
||||
const pending = buffer;
|
||||
buffer = [];
|
||||
for (const event of pending) {
|
||||
onEvent(event);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
override async getReport(id: string): Promise<Report> {
|
||||
const { data, error } = await this.#client.GET(
|
||||
"/api/runs/{run_id}/report",
|
||||
{ params: { path: { run_id: id } } },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: report not ready for run ${id}`);
|
||||
}
|
||||
const markdown = (data as { run_id: string; markdown: string }).markdown;
|
||||
return {
|
||||
id,
|
||||
runId: id,
|
||||
title: "Research Report",
|
||||
sections: [{ heading: "Report", body: markdown }],
|
||||
citations: [],
|
||||
};
|
||||
}
|
||||
|
||||
override async getLedger(id: string): Promise<CitationLedger> {
|
||||
const { data, error } = await this.#client.GET(
|
||||
"/api/runs/{run_id}/ledger",
|
||||
{ params: { path: { run_id: id } } },
|
||||
);
|
||||
if (error || !data) {
|
||||
throw new Error(`live: ledger not ready for run ${id}`);
|
||||
}
|
||||
const backend = data as {
|
||||
run_id: string;
|
||||
generated_at: string;
|
||||
entries: Array<{
|
||||
url: string;
|
||||
visited_at: string;
|
||||
passages: string[];
|
||||
claims_supported: number;
|
||||
health: string;
|
||||
authority_tier: string;
|
||||
}>;
|
||||
};
|
||||
return {
|
||||
runId: backend.run_id,
|
||||
generatedAt: backend.generated_at,
|
||||
citations: backend.entries.map((entry, index) => ({
|
||||
id: `c-${index + 1}`,
|
||||
url: entry.url,
|
||||
title: entry.url,
|
||||
site: entry.url,
|
||||
passageId: entry.passages[0] ?? "",
|
||||
quote: "",
|
||||
verification: (entry.health === "live"
|
||||
? "live"
|
||||
: entry.health === "stale"
|
||||
? "stale"
|
||||
: "dead") as CitationLedger["citations"][number]["verification"],
|
||||
authorityTier: (entry.authority_tier === "official"
|
||||
? 1
|
||||
: entry.authority_tier === "reference"
|
||||
? 2
|
||||
: 3) as CitationLedger["citations"][number]["authorityTier"],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
override async exportReport(
|
||||
id: string,
|
||||
format: "markdown" | "json",
|
||||
): Promise<{ filename: string; content: string }> {
|
||||
if (format === "markdown") {
|
||||
const report = await this.getReport(id);
|
||||
const body = report.sections.map((s) => `## ${s.heading}\n\n${s.body}`).join("\n\n");
|
||||
return { filename: `researchx-${id}.md`, content: `# Research Report\n\n${body}\n` };
|
||||
}
|
||||
const [report, ledger] = await Promise.all([this.getReport(id), this.getLedger(id)]);
|
||||
const payload = {
|
||||
runId: id,
|
||||
report: report.sections,
|
||||
ledger: {
|
||||
runId: ledger.runId,
|
||||
generatedAt: ledger.generatedAt,
|
||||
citations: ledger.citations,
|
||||
},
|
||||
};
|
||||
return {
|
||||
filename: `researchx-${id}.json`,
|
||||
content: JSON.stringify(payload, null, 2),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Shared test fixtures for live-binding contract tests. */
|
||||
|
||||
export interface BackendRun {
|
||||
id: string;
|
||||
status: string;
|
||||
question: string;
|
||||
methodology: string;
|
||||
plan: {
|
||||
sub_questions: string[];
|
||||
source_universe: string[];
|
||||
outline: string[];
|
||||
} | null;
|
||||
started_at: string;
|
||||
}
|
||||
Reference in New Issue
Block a user