diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..d1990d8 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(bash -n sync.sh)", + "Bash(./sync.sh --check)" + ] + } +} diff --git a/.claude/skills/seclai-changelog/SKILL.md b/.claude/skills/seclai-changelog/SKILL.md new file mode 100644 index 0000000..e7cccf1 --- /dev/null +++ b/.claude/skills/seclai-changelog/SKILL.md @@ -0,0 +1,147 @@ +--- +name: seclai-changelog +description: Write or update CHANGELOG.md in a Seclai SDK repo (seclai-python, seclai-javascript, seclai-go, seclai-csharp, seclai-cli, seclai-mcp) using the Common Changelog format. Use when adding a changelog entry, preparing a release, backfilling history from version tags, or finishing an OpenAPI spec sync in one of these repos. +--- + +# Seclai changelog entries + +All six Seclai SDK repos keep a root `CHANGELOG.md` in [Common Changelog](https://common-changelog.org) format. This skill covers writing a new entry and backfilling from tags. + +## Format rules + +- File starts with `# Changelog`, then releases sorted latest-first. +- Release heading: `## [1.4.0] - 2026-07-25` — semver without a `v`, ISO date. +- **No `Unreleased` section.** Entries are written in the PR that ships them, under the version that PR will become (see "Determine the version"). +- Change groups are third-level headings, only these four, always in this order: + `### Changed`, `### Added`, `### Removed`, `### Fixed`. +- A group heading is followed by an unordered list and nothing else. +- Entry form: `- Change ([ref](url))`. Imperative present tense, self-describing — it must read correctly without its group heading. "Support CentOS", never "Support of CentOS" or "Added support". +- Breaking changes take a `**Breaking:**` prefix and sort first within their group. Otherwise sort by importance. +- A release may open with a one-sentence italic notice instead of, or before, its groups — used for first releases (`_Initial release._`) and no-op version bumps. +- Version links are reference-style at the bottom: `[1.4.0]: https://github.com/seclai//releases/tag/1.4.0` (tags carry no `v` prefix). +- Authors are omitted — these are effectively single-contributor repos. + +## Determine the version + +Do not guess the next version. Releases are cut by `seclai/github-tag-action` in `.github/workflows/main-build.yaml`, which reads the **merge commit message**: + +- contains `#major` → major bump +- contains `#minor` → minor bump +- otherwise → `DEFAULT_BUMP: patch` + +So a PR that adds endpoints must say `#minor` in its title/merge commit, or the heading you write will not match the tag that gets cut. Confirm the intended bump with the user when it isn't stated, and flag the mismatch risk if the PR title lacks the keyword. + +Check `git tag --sort=-v:refname | head -1` for the current latest, then apply the bump. + +## Derive entries from diffs, not from release notes + +`gh release view` bodies are `--generate-notes` output — just "PR #N by @author". They are useless as entry text. Commit subjects like "2026 05 22 api sync" are equally useless. Always read the actual diff. + +Extract the public API delta for a range. Per repo: + +| Repo | Path | Pattern | +| --- | --- | --- | +| seclai-javascript | `src/client.ts` | `async ([a-zA-Z_][a-zA-Z0-9_]*)\(` | +| seclai-python | `seclai/seclai.py` | `^ (async )?def ([a-z][a-z0-9_]*)` | +| seclai-go | `*.go` | `^func \(c \*Client\) ([A-Z][A-Za-z0-9]*)\(` | +| seclai-csharp | `src/Seclai/SeclaiClient.cs` | `public (async )?[A-Za-z<>,? ]+ ([A-Z][A-Za-z0-9]*)\(` | +| seclai-cli | `src/commands/` | one file or subcommand per feature | +| seclai-mcp | `src/` | registered tool names | + +```bash +# methods added between two tags +git diff PREV TAG -- src/ | grep "^+" | grep -oE '' | sort -u +# and removed +git diff PREV TAG -- src/ | grep "^-" | grep -oE '' | sort -u +``` + +A name in **both** lists was modified, not removed — check the signature diff before writing a `Removed` entry. Renames and reordering produce false positives constantly. + +Also diff the type aliases (`src/types.ts`, `seclai/models`, etc.) and the bundled `openapi/seclai.openapi.json` — new schemas often mean new public types worth an entry even when no method changed. + +## Classify + +- New method, option, type export, or capability → **Added** +- Changed signature, default, accepted type, or behavior of something that already worked → **Changed** +- Deleted public surface → **Removed** +- It was broken and now works → **Fixed** + +Judgment calls that have come up: + +- A wrong default host or a wrong request path is **Fixed** — requests were failing — not Changed. +- Making a required parameter optional (via overload) is **Changed**, not Added. +- Exporting a type that should already have been exported is **Fixed**. +- Adding an endpoint to the bundled spec without a client method is worth its own entry; say so plainly rather than implying the method exists. +- A version bump with no code change gets a notice, not a group: `_Stable release. No functional changes since 0.0.1._` + +## Write + +Reference the PR when there is one (`([#9](https://github.com/seclai//pull/9))`), otherwise the short commit SHA (`([`36bff73`](https://github.com/seclai//commit/36bff73))`). For a PR not yet opened, omit the reference — never invent a number. + +Group related additions into one entry when they ship as a unit (e.g. eight email-domain methods), and give standalone capabilities their own line. Aim for entries a user scanning for "what changed for me" can act on. + +## Check the surface before you describe it + +The changelog is the last gate before a release, so use the entries you are +writing as a prompt to spot-check the public surface they describe. Fix these, or +raise them, before they ship — each one is far cheaper now than after consumers +depend on it. + +**Return the shape the spec declares.** Look up the endpoint's 2xx response schema +and mirror it: + +| Spec response | Return type | +| --- | --- | +| `$ref` to a schema | that aliased type — never `unknown` | +| `type: object`, `additionalProperties: true` | `Record` | +| `additionalProperties: {type: T}` | `Record` | +| `type: array` | `T[]` / `unknown[]` — **not** `Record` | + +A hand-written wrapper returning `unknown` where the generator had a real type +available is a bug, not a style choice. + +**Widening `unknown` is breaking.** Changing a released method from `unknown` to +`Record` breaks consumer code that does `result as SomeInterface` +— TypeScript rejects it with TS2352 and demands `as unknown as SomeInterface`. +That makes it a `#major`, never a patch or minor. New methods have no consumers +yet, so type them correctly from the start rather than inheriting a neighbour's +`unknown`. + +**Server-defaulted request fields must be optional.** openapi-typescript emits any +property carrying a `default` as **required**, even when the spec leaves it out of +`required`. Callers then have to pass a value the server would have defaulted. +Wrap the generated request in an input type: + +```ts +export type AddEmailDomainInput = Pick & + Partial>; +``` + +See `AddEmailDomainInput` and `CreateExperimentInput` in `src/types.ts`. + +**Compile the doc examples.** `npm run typecheck` covers `src/` and `tests/`, not +README or changelog snippets. Paste any example you write into a scratch `.ts` +that imports from `../src/index` and compile it — that is how the +`delegated`-is-required bug above was found, after the snippet had already shipped +in a PR. + +## Validate + +Run the bundled checker from the repo root before declaring done: + +```bash +python3 .claude/skills/seclai-changelog/validate.py CHANGELOG.md +``` + +That path holds in every SDK repo — this skill is vendored there from [`seclai/sdk-tools`](https://github.com/seclai/sdk-tools). When working inside `sdk-tools` itself, the canonical copy is `skills/seclai-changelog/validate.py`. + +It verifies heading format, descending version order, group names and ordering, `Breaking:` sorting, absence of an `Unreleased` section, and that every release has exactly one matching link definition. It exits non-zero on error, so the same invocation works as a CI gate. + +Also confirm `CHANGELOG.md` is in the published artifact list — `files` in `package.json` for the JS/CLI/MCP repos, the packaging config for the others. + +## Backfilling from tags + +1. `git tag --sort=-v:refname` and `gh release list` for versions and dates. Use the GitHub release published date (UTC) as the entry date. +2. Walk consecutive tag pairs oldest-first, extracting the API delta for each as above. +3. Map each range to its PR via `gh release view --json body`, which does at least carry the PR number reliably. +4. The earliest release gets `_Initial release._`. diff --git a/.claude/skills/seclai-changelog/validate.py b/.claude/skills/seclai-changelog/validate.py new file mode 100755 index 0000000..6ad2218 --- /dev/null +++ b/.claude/skills/seclai-changelog/validate.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Validate a Common Changelog file as used by the Seclai SDK repos. + +Usage: python3 validate.py [CHANGELOG.md] + +Exits non-zero and prints one line per problem. Warnings do not affect the +exit code. +""" +import re +import sys + +GROUPS = ["Changed", "Added", "Removed", "Fixed"] +RELEASE_RE = re.compile(r"^## \[([^\]]+)\] - (\d{4}-\d{2}-\d{2})\s*$") +GROUP_RE = re.compile(r"^### (.+?)\s*$") +LINKDEF_RE = re.compile(r"^\[([^\]]+)\]:\s+(\S+)\s*$") +SEMVER_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$") + + +def semver_key(v): + m = SEMVER_RE.match(v) + return tuple(int(g) for g in m.groups()) if m else (-1, -1, -1) + + +def main(): + path = sys.argv[1] if len(sys.argv) > 1 else "CHANGELOG.md" + try: + lines = open(path, encoding="utf-8").read().split("\n") + except OSError as e: + print(f"error: cannot read {path}: {e}") + return 1 + + errors, warnings = [], [] + releases, linkdefs = [], [] + cur = None + seen_groups = [] + group_entries = [] + in_group = None + + def close_group(): + if in_group is None: + return + if not group_entries: + errors.append(f"{cur}: '### {in_group}' has no entries") + breaking = [i for i, e in enumerate(group_entries) if e.startswith("**Breaking:**")] + if breaking and breaking != list(range(len(breaking))): + errors.append(f"{cur}: breaking changes must sort first under '### {in_group}'") + + if not lines or lines[0].strip() != "# Changelog": + errors.append("file must start with '# Changelog'") + + for n, line in enumerate(lines, 1): + if line.strip().lower().startswith("## [unreleased") or line.strip().lower() == "## unreleased": + errors.append(f"line {n}: Common Changelog has no Unreleased section") + continue + + m = RELEASE_RE.match(line) + if m: + close_group() + cur, seen_groups, in_group, group_entries = m.group(1), [], None, [] + if not SEMVER_RE.match(cur): + errors.append(f"line {n}: '{cur}' is not a bare semver version") + releases.append(cur) + continue + + if line.startswith("## "): + errors.append(f"line {n}: malformed release heading: {line.strip()!r}") + continue + + m = GROUP_RE.match(line) + if m: + close_group() + g = m.group(1) + in_group, group_entries = g, [] + if cur is None: + errors.append(f"line {n}: '### {g}' before any release heading") + continue + if g not in GROUPS: + errors.append(f"{cur}: unknown group '{g}' (allowed: {', '.join(GROUPS)})") + continue + i = GROUPS.index(g) + if g in [GROUPS[j] for j in seen_groups]: + errors.append(f"{cur}: duplicate group '{g}'") + elif seen_groups and i < seen_groups[-1]: + errors.append( + f"{cur}: '{g}' out of order — required order is {' > '.join(GROUPS)}" + ) + seen_groups.append(i) + continue + + m = LINKDEF_RE.match(line) + if m: + linkdefs.append(m.group(1)) + continue + + if line.startswith("- ") and in_group is not None: + entry = line[2:].strip() + group_entries.append(entry) + if entry.endswith("."): + warnings.append(f"{cur}: entry ends with a period: {entry[:60]!r}") + + close_group() + + ordered = sorted(releases, key=semver_key, reverse=True) + if releases != ordered: + errors.append(f"releases not sorted latest-first: {' '.join(releases)}") + + for v in releases: + c = linkdefs.count(v) + if c == 0: + errors.append(f"{v}: missing link definition at the bottom of the file") + elif c > 1: + errors.append(f"{v}: {c} link definitions, expected 1") + for d in linkdefs: + if d not in releases: + errors.append(f"orphan link definition [{d}] with no matching release") + + for w in warnings: + print(f"warning: {w}") + for e in errors: + print(f"error: {e}") + + if errors: + print(f"\n{len(errors)} error(s) in {path}") + return 1 + print(f"{path}: OK — {len(releases)} releases, format valid") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.claude/skills/seclai-sdk-sync/SKILL.md b/.claude/skills/seclai-sdk-sync/SKILL.md new file mode 100644 index 0000000..675b0c9 --- /dev/null +++ b/.claude/skills/seclai-sdk-sync/SKILL.md @@ -0,0 +1,108 @@ +--- +name: seclai-sdk-sync +description: Sync a Seclai SDK (seclai-python, seclai-javascript, seclai-go, seclai-csharp, seclai-cli, seclai-mcp) to a new OpenAPI spec, or add endpoints to one. Use when a new openapi/seclai.openapi.json has been copied in, when asked to add missing endpoints or check endpoint coverage, or when auditing an SDK against the API spec. +--- + +# Syncing a Seclai SDK to a new spec + +Run the analysis with `sdksync.py`, bundled next to this file, rather than +hand-rolling greps. Every ad-hoc parity regex written so far has missed methods +with multi-line signatures. + +```bash +S=.claude/skills/seclai-sdk-sync/sdksync.py # vendored into each SDK repo + +python3 $S spec-diff HEAD # what the new spec changed +python3 $S parity . # spec paths with no request call +python3 $S api-delta 1.3.0 # public methods added since a tag +``` + +`parity` exits non-zero when a path is unimplemented, so it works as a CI gate. +Run it over the **whole spec**, never just the diff — `GET /me` sat unimplemented +in both the Python and JavaScript SDKs for months because each sync only looked at +its own new paths. + +## The repos are not uniform + +| Repo | Client | Bundles spec | Notes | +| --- | --- | --- | --- | +| seclai-python | generated + hand-written wrappers | yes | `make generate`, then black | +| seclai-javascript | types generated, methods hand-written | yes | `npm run generate` | +| seclai-go | hand-written | yes | | +| seclai-csharp | hand-rolled from the start | **no** | no codegen library was suitable; covers a subset of the API by design | +| seclai-cli | wraps `@seclai/sdk` | no | coverage question is command-to-SDK-method | +| seclai-mcp | no client source | no | | + +For repos without a bundled spec, point at one: +`--spec ../seclai-python/openapi/seclai.openapi.json`. + +## Workflow + +1. **Confirm the spec is identical** across the repos that bundle it. They must + not diverge — a local edit is always wrong; fix the spec upstream in `seclai`. +2. **`spec-diff`** to see added/removed/changed paths and schema property changes. +3. **Regenerate**, per repo: + - python: `make generate`, then **immediately** `poetry run black .` — the + generator formats with ruff but the repo commits black, so raw output shows + ~240 changed files that collapse to ~60 real ones. + - javascript: `npm run generate` (no churn; types only). + - go / csharp: nothing to regenerate. +4. **`parity`** to list what is missing. Implement every path, not just the ones + that look interesting — binary/stream endpoints with no JSON schema are the + ones that get skipped. +5. **Write the methods.** In Python they must land in **both** `Seclai` and + `AsyncSeclai`. Generating both from a single table of method definitions is + the reliable way to keep them identical; hand-writing 2×N methods drifts. +6. **Tests** — sync and async for each method, asserting verb, path, query params + and body. Python uses `httpx.MockTransport`; JavaScript uses a `makeClient` + fetch stub. +7. **README** — one section per new endpoint group. +8. **Changelog** — use the `seclai-changelog` skill. +9. **Gate**: `make lint && make test` (python) or + `npm run typecheck && npm test && npm run build` (javascript). Re-run `parity` + and confirm zero missing. + +## Naming + +A new method name sets precedent for all six SDKs — the first repo synced defines +it and the rest should follow. Check whether a sibling already named the same +endpoint before inventing one, and prefer the sibling's name transliterated to +local conventions (`searchDocs` / `search_docs` / `SearchDocs`). + +## Typing conventions + +- Return the shape the spec declares: a `$ref` becomes the aliased type, never an + untyped map. See the `seclai-changelog` skill for the full table and the + breaking-change rules. +- **JavaScript:** openapi-typescript emits any property carrying a `default` as + **required**, even when the spec omits it from `required`. Wrap the generated + request so server-defaulted fields stay optional: + `Pick & Partial>` — see `AddEmailDomainInput`. +- Query params are camelCase in the method signature, snake_case on the wire. + +## Repo gotchas + +**seclai-python** + +- `poetry run black .` will reformat the **subtree-vendored** `.claude/skills/` + files and silently drift them from canonical. `.claude` must stay excluded in + black `extend-exclude`, ruff `exclude`, and mypy `exclude`. +- `make generate` always prints + `Unable to parse schema … duplicate models with name "FileUploadResponse"`. + Pre-existing and non-fatal — `routers__api__sources__` and + `routers__api__contents__FileUploadResponse` share a title. Generation completes. +- `seclai/_generated/seclai_api_client/` is **not** regenerated and nothing + imports it. It is stale and safe to ignore; do not treat it as a source of truth. +- mypy rejects assigning the result of a `-> None` method, so tests for + 204-returning endpoints must call without binding a variable. + +**seclai-javascript** + +- `npm run typecheck` does not cover README snippets. Paste any example into a + scratch `.ts` importing from `../src/index` and compile it before claiming it works. + +## Release + +The version comes from the merge commit message, read by `seclai/github-tag-action` +with `DEFAULT_BUMP: patch`. A sync that adds endpoints needs `#minor` in the PR +title, or it ships as a patch and the changelog heading will not match the tag. diff --git a/.claude/skills/seclai-sdk-sync/sdksync.py b/.claude/skills/seclai-sdk-sync/sdksync.py new file mode 100755 index 0000000..d4fffa5 --- /dev/null +++ b/.claude/skills/seclai-sdk-sync/sdksync.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python3 +"""Analysis helpers for syncing a Seclai SDK to a new OpenAPI spec. + +Subcommands: + parity spec paths that have no request call in the hand-written client + spec-diff paths and schemas added/removed/changed between two spec revisions + api-delta public client methods added/removed between two git revisions + +Stdlib only, so it runs in every SDK repo regardless of language toolchain. + +Scope: parity and api-delta understand the four SDKs that issue HTTP requests +directly — python, javascript, go, csharp. seclai-cli wraps @seclai/sdk and +seclai-mcp ships no client source, so both are reported as not-applicable +rather than silently passing. +""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path + +VERBS = ("GET", "POST", "PUT", "PATCH", "DELETE") + +# ── Language table ─────────────────────────────────────────────────────────── +# `sources` are HAND-WRITTEN client files only. Generated trees must never be +# scanned: they contain a module per endpoint and would make parity always pass. +LANGS = { + "python": { + "detect": "seclai/seclai.py", + "sources": ["seclai/seclai.py"], + "method_re": r"^[ \t]+(?:async )?def ([a-z][a-z0-9_]*)\(", + "verb_re": r'"(GET|POST|PUT|PATCH|DELETE)"', + }, + "javascript": { + "detect": "src/client.ts", + "sources": ["src/client.ts"], + "method_re": r"^[ \t]+(?:async )?\*?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", + "verb_re": r'"(GET|POST|PUT|PATCH|DELETE)"', + }, + "go": { + "detect": "client.go", + "sources": ["*.go"], + "exclude": ["*_test.go"], + "method_re": r"^func \(c \*Client\) ([A-Z][A-Za-z0-9]*)\(", + "verb_re": r"http\.Method(Get|Post|Put|Patch|Delete)", + }, + "csharp": { + "detect": "src/Seclai/SeclaiClient.cs", + "sources": ["src/Seclai/*.cs"], + "method_re": r"public (?:async )?[\w<>,?\[\]. ]+ ([A-Z][A-Za-z0-9]*)\s*\(", + "verb_re": r"HttpMethod\.(Get|Post|Put|Patch|Delete)", + }, +} + +NOT_APPLICABLE = { + "seclai-cli": "wraps @seclai/sdk; coverage is SDK-method-to-command, not spec-path", + "seclai-mcp": "ships no client source", +} + + +def die(msg: str) -> None: + print(f"error: {msg}", file=sys.stderr) + raise SystemExit(2) + + +def detect_lang(repo: Path) -> str | None: + for name, cfg in LANGS.items(): + if (repo / cfg["detect"]).exists(): + return name + return None + + +def source_files(repo: Path, cfg: dict) -> list[Path]: + out: list[Path] = [] + for pat in cfg["sources"]: + out.extend(sorted(repo.glob(pat)) if "*" in pat else ([repo / pat] if (repo / pat).exists() else [])) + for pat in cfg.get("exclude", []): + excl = set(repo.glob(pat)) + out = [p for p in out if p not in excl] + return out + + +def normalise(path: str) -> str: + """Collapse every placeholder form to `{}` so paths compare across languages.""" + path = re.sub(r"\$\{[^}]*\}", "{}", path) # JS template ${agentId} + path = re.sub(r"\{[^}]*\}", "{}", path) # py f-string / C# interpolation / spec + path = re.sub(r"%[sdv]", "{}", path) # go fmt.Sprintf + return path.rstrip("/") or "/" + + +def extract_paths(text: str, verb_re: str) -> dict[str, set[str]]: + """Map normalised path -> set of verbs seen near its occurrences. + + Verb association is best-effort: it scans a window around each occurrence. + Absence of a verb is reported as a warning, never as a hard miss. + """ + found: dict[str, set[str]] = {} + for m in re.finditer(r"""["'`](/[A-Za-z0-9_\-/{}$%.]*)["'`]""", text): + p = normalise(m.group(1)) + if p == "/" or not p.startswith("/"): + continue + window = text[max(0, m.start() - 220): m.end() + 60] + verbs = {v.upper() for v in re.findall(verb_re, window)} + found.setdefault(p, set()).update(verbs) + return found + + +def load_spec(ref: str | None, path: str, repo: Path) -> dict: + if ref: + try: + blob = subprocess.check_output( + ["git", "-C", str(repo), "show", f"{ref}:{path}"], stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError: + die(f"cannot read {path} at {ref}") + return json.loads(blob) + f = Path(path) if Path(path).is_absolute() else repo / path + if not f.exists(): + die(f"no spec at {f}\n" + " Only seclai-python, seclai-javascript and seclai-go bundle the spec.\n" + " For the others, point at one explicitly, e.g.\n" + " --spec ../seclai-python/openapi/seclai.openapi.json") + return json.loads(f.read_text()) + + +# ── parity ─────────────────────────────────────────────────────────────────── +def cmd_parity(args) -> int: + repo = Path(args.repo).resolve() + name = repo.name + if name in NOT_APPLICABLE: + print(f"{name}: not applicable — {NOT_APPLICABLE[name]}") + return 0 + lang = args.lang or detect_lang(repo) + if not lang: + die(f"cannot detect SDK language in {repo} (looked for " + + ", ".join(c["detect"] for c in LANGS.values()) + ")") + cfg = LANGS[lang] + files = source_files(repo, cfg) + if not files: + die(f"no client sources found for {lang} in {repo}") + + spec = load_spec(args.rev, args.spec, repo) + text = "\n".join(f.read_text(errors="replace") for f in files) + impl = extract_paths(text, cfg["verb_re"]) + + missing, partial = [], [] + total = 0 + for p, ops in sorted(spec.get("paths", {}).items()): + verbs = {v.upper() for v in ops if v in ("get", "post", "put", "patch", "delete")} + if not verbs: + continue + total += len(verbs) + norm = normalise(p) + if norm not in impl: + missing += [f"{v} {p}" for v in sorted(verbs)] + else: + seen = impl[norm] + if seen and not verbs <= seen: + partial += [f"{v} {p}" for v in sorted(verbs - seen)] + + print(f"{name} [{lang}] — {len(files)} client file(s), " + f"{total} spec operations across {len(spec.get('paths', {}))} paths") + if missing: + print(f"\nMISSING — no request call for this path ({len(missing)}):") + for m in missing: + print(f" {m}") + if partial and not args.quiet_partial: + print(f"\nverb not detected near an existing path ({len(partial)}) " + f"— best-effort, verify by hand:") + for m in partial: + print(f" {m}") + if not missing: + print("\nfull path parity") + return 1 if missing else 0 + + +# ── spec-diff ──────────────────────────────────────────────────────────────── +def cmd_spec_diff(args) -> int: + repo = Path(args.repo).resolve() + old = load_spec(args.old, args.spec, repo) + new = load_spec(args.new, args.spec, repo) if args.new else load_spec(None, args.spec, repo) + + op, np_ = set(old.get("paths", {})), set(new.get("paths", {})) + os_, ns_ = set(old.get("components", {}).get("schemas", {})), set(new.get("components", {}).get("schemas", {})) + + def ops(spec, p): + return sorted(v.upper() for v in spec["paths"][p] if v in ("get", "post", "put", "patch", "delete")) + + print(f"paths: {len(op)} -> {len(np_)} schemas: {len(os_)} -> {len(ns_)}") + + if np_ - op: + print(f"\nADDED PATHS ({len(np_ - op)}):") + for p in sorted(np_ - op): + print(f" {p} [{', '.join(ops(new, p))}]") + if op - np_: + print(f"\nREMOVED PATHS ({len(op - np_)}):") + for p in sorted(op - np_): + print(f" {p}") + + changed = [] + for p in sorted(np_ & op): + if json.dumps(old["paths"][p], sort_keys=True) != json.dumps(new["paths"][p], sort_keys=True): + oo, nn = set(ops(old, p)), set(ops(new, p)) + note = "verbs " + ", ".join(sorted(nn - oo)) if nn - oo else "description/params only" + changed.append(f" {p} ({note})") + if changed: + print(f"\nCHANGED PATHS ({len(changed)}):") + print("\n".join(changed)) + + if ns_ - os_: + print(f"\nADDED SCHEMAS ({len(ns_ - os_)}):") + for s in sorted(ns_ - os_): + print(f" {s}") + if os_ - ns_: + print(f"\nREMOVED SCHEMAS ({len(os_ - ns_)}):") + for s in sorted(os_ - ns_): + print(f" {s}") + + prop_changes = [] + for k in sorted(ns_ & os_): + o = set((old["components"]["schemas"][k].get("properties") or {})) + n = set((new["components"]["schemas"][k].get("properties") or {})) + if o != n: + bits = [] + if n - o: + bits.append("+" + ",".join(sorted(n - o))) + if o - n: + bits.append("-" + ",".join(sorted(o - n))) + prop_changes.append(f" {k}: {' '.join(bits)}") + if prop_changes: + print(f"\nSCHEMA PROPERTY CHANGES ({len(prop_changes)}):") + print("\n".join(prop_changes)) + return 0 + + +# ── api-delta ──────────────────────────────────────────────────────────────── +def methods_at(repo: Path, rev: str | None, cfg: dict, files: list[Path]) -> set[str]: + names: set[str] = set() + for f in files: + rel = f.relative_to(repo) + if rev: + try: + text = subprocess.check_output( + ["git", "-C", str(repo), "show", f"{rev}:{rel}"], + stderr=subprocess.DEVNULL).decode("utf-8", "replace") + except subprocess.CalledProcessError: + continue + else: + text = f.read_text(errors="replace") + for m in re.finditer(cfg["method_re"], text, re.M): + n = m.group(1) + if not n.startswith("_"): + names.add(n) + return names + + +def cmd_api_delta(args) -> int: + repo = Path(args.repo).resolve() + name = repo.name + if name in NOT_APPLICABLE: + print(f"{name}: not applicable — {NOT_APPLICABLE[name]}") + return 0 + lang = args.lang or detect_lang(repo) + if not lang: + die(f"cannot detect SDK language in {repo}") + cfg = LANGS[lang] + files = source_files(repo, cfg) + + old = methods_at(repo, args.old, cfg, files) + new = methods_at(repo, args.new, cfg, files) + + added, removed = sorted(new - old), sorted(old - new) + label_new = args.new or "working tree" + print(f"{name} [{lang}] {args.old} -> {label_new}") + print(f"\nADDED ({len(added)}):") + for n in added: + print(f" {n}") + if removed: + print(f"\nREMOVED ({len(removed)}):") + for n in removed: + print(f" {n}") + print("\n note: a name in both lists was renamed or re-signatured, not deleted —" + "\n check the signature diff before writing a Removed changelog entry.") + return 0 + + +def main() -> int: + ap = argparse.ArgumentParser(prog="sdksync", description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + sub = ap.add_subparsers(dest="cmd", required=True) + + p = sub.add_parser("parity", help="spec paths with no request call in the client") + p.add_argument("repo", nargs="?", default=".") + p.add_argument("--spec", default="openapi/seclai.openapi.json") + p.add_argument("--rev", help="read the spec from this git rev instead of the working tree") + p.add_argument("--lang", choices=list(LANGS)) + p.add_argument("--quiet-partial", action="store_true", help="suppress the best-effort verb warnings") + p.set_defaults(func=cmd_parity) + + p = sub.add_parser("spec-diff", help="paths/schemas added, removed or changed between revisions") + p.add_argument("old", help="git rev of the older spec") + p.add_argument("new", nargs="?", help="git rev of the newer spec (default: working tree)") + p.add_argument("--repo", default=".") + p.add_argument("--spec", default="openapi/seclai.openapi.json") + p.set_defaults(func=cmd_spec_diff) + + p = sub.add_parser("api-delta", help="public client methods added/removed between revisions") + p.add_argument("old", help="git rev") + p.add_argument("new", nargs="?", help="git rev (default: working tree)") + p.add_argument("--repo", default=".") + p.add_argument("--lang", choices=list(LANGS)) + p.set_defaults(func=cmd_api_delta) + + args = ap.parse_args() + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b19702c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,144 @@ +# Changelog + +## [1.4.0] - 2026-07-25 + +### Changed + +- Sync the bundled OpenAPI spec, adding 22 paths and 22 schemas + +### Added + +- Add `get_me()` returning the authenticated user's account id and organization memberships +- Add `disable_agent()`, `enable_agent()`, and `get_agent_callers()` to pause and resume an agent across every trigger path +- Add `set_email_trigger_config()` to set the alias, sender allowlist, and inbound-handling flags on an `EMAIL_RECEIVED` trigger +- Add agent-email opt-out methods `list_agent_email_optouts()` and `remove_agent_email_optout()` +- Add inbound sender blocklist methods `list_blocked_email_senders()`, `block_email_sender()`, `unblock_email_sender()`, and `set_auto_block_mode()` +- Add inbound-email observability methods `list_inbound_email_rejections()`, `get_inbound_email_status()`, `cancel_queued_email_runs()`, and `resume_inbound_email()` +- Add email domain management: `list_email_domains()`, `add_email_domain()`, `remove_email_domain()`, `verify_email_domain()`, `set_primary_email_domain()`, `use_shared_email_domain()`, `send_email_domain_test_email()`, and `get_dmarc_summary()` +- Add `get_generation_tiers()` mapping each media-generation modality and tier to its model and cost +- Add `search_docs()` for keyword or semantic search over the Seclai documentation + +## [1.3.0] - 2026-06-05 + +### Added + +- Add `get_agent_attachment_references()` to read an agent's static attachment-reference contract before staging uploads ([#9](https://github.com/seclai/seclai-python/pull/9)) +- Add `download_agent_run_attachment()` for a file emitted by a run step ([#9](https://github.com/seclai/seclai-python/pull/9)) +- Add `delete_experiment()` to soft-delete a model playground experiment ([#9](https://github.com/seclai/seclai-python/pull/9)) + +## [1.2.0] - 2026-05-22 + +### Added + +- Add `preview_import_agent()` to dry-run an agent definition import and surface unresolved entity refs ([#8](https://github.com/seclai/seclai-python/pull/8)) + +## [1.1.4] - 2026-04-24 + +### Added + +- Add `list_models()` and `get_model()` for the model catalog ([#7](https://github.com/seclai/seclai-python/pull/7)) +- Add model playground methods `list_experiments()`, `create_experiment()`, `get_experiment()`, and `cancel_experiment()` ([#7](https://github.com/seclai/seclai-python/pull/7)) + +## [1.1.3] - 2026-04-02 + +### Added + +- Add `export_agent()` returning a portable JSON snapshot of an agent definition ([#6](https://github.com/seclai/seclai-python/pull/6)) + +## [1.1.2] - 2026-03-27 + +### Changed + +- Default the SSO domain, client id, and region so a profile only needs `sso_account_id` ([#5](https://github.com/seclai/seclai-python/pull/5)) + +### Added + +- Add `GET /me` to the bundled OpenAPI spec; the corresponding `get_me()` client method arrived in 1.4.0 ([#5](https://github.com/seclai/seclai-python/pull/5)) + +## [1.1.1] - 2026-03-26 + +### Added + +- Add OAuth SSO authentication with `~/.seclai/config` profiles, an on-disk token cache, and automatic refresh ([#4](https://github.com/seclai/seclai-python/pull/4)) +- Add an `account_id` option, sent as the `X-Account-Id` header, to switch organization account context ([#4](https://github.com/seclai/seclai-python/pull/4)) + +## [1.1.0] - 2026-03-23 + +### Added + +- Expand endpoint coverage to knowledge bases, memory banks, sources, source exports, embedding migrations, content, solutions, alerts, governance, evaluations, and the AI assistants ([#3](https://github.com/seclai/seclai-python/pull/3)) +- Add `run_streaming_agent()`, an async generator yielding every SSE event of a run ([#3](https://github.com/seclai/seclai-python/pull/3)) +- Add `run_agent_and_poll()` for environments where SSE is impractical ([#3](https://github.com/seclai/seclai-python/pull/3)) +- Add `paginate()` to iterate a paginated endpoint automatically ([#3](https://github.com/seclai/seclai-python/pull/3)) +- Add `search()` across all resource types in an account ([#3](https://github.com/seclai/seclai-python/pull/3)) + +## [1.0.6] - 2026-01-30 + +### Added + +- Add `upload_file_to_content()` to replace existing content with a file upload ([`e0d353a`](https://github.com/seclai/seclai-python/commit/e0d353a)) +- Add a `metadata` argument to the upload methods ([`e0d353a`](https://github.com/seclai/seclai-python/commit/e0d353a)) + +### Fixed + +- Correct type annotations on the upload and content methods ([`206e020`](https://github.com/seclai/seclai-python/commit/206e020)) + +## [1.0.5] - 2026-01-27 + +### Changed + +- Accept a run id alone in `get_agent_run()` and `delete_agent_run()`; the agent id is no longer required ([`51926b8`](https://github.com/seclai/seclai-python/commit/51926b8)) + +## [1.0.4] - 2026-01-27 + +### Added + +- Add an `include_step_outputs` argument to `get_agent_run()` ([`d3b4501`](https://github.com/seclai/seclai-python/commit/d3b4501)) + +## [1.0.3] - 2026-01-27 + +### Added + +- Add `run_streaming_agent_and_wait()` to block until a streaming run completes ([`f7850df`](https://github.com/seclai/seclai-python/commit/f7850df)) + +### Fixed + +- Drop the `/api` prefix from request paths so they match the deployed API ([`4a33852`](https://github.com/seclai/seclai-python/commit/4a33852)) +- Correct the file upload endpoint ([`ea50e25`](https://github.com/seclai/seclai-python/commit/ea50e25)) + +## [1.0.2] - 2026-01-12 + +### Removed + +- Remove build artifacts that had been committed to the repository ([`d946d55`](https://github.com/seclai/seclai-python/commit/d946d55)) + +## [1.0.1] - 2026-01-12 + +### Added + +- Add a documentation homepage link to the package metadata ([`cc8e28f`](https://github.com/seclai/seclai-python/commit/cc8e28f)) + +## [1.0.0] - 2026-01-12 + +_Stable release. Packaging, CI, and documentation deployment only; no API changes since 0.0.1._ + +## [0.0.1] - 2026-01-12 + +_Initial release._ + +[1.4.0]: https://github.com/seclai/seclai-python/releases/tag/1.4.0 +[1.3.0]: https://github.com/seclai/seclai-python/releases/tag/1.3.0 +[1.2.0]: https://github.com/seclai/seclai-python/releases/tag/1.2.0 +[1.1.4]: https://github.com/seclai/seclai-python/releases/tag/1.1.4 +[1.1.3]: https://github.com/seclai/seclai-python/releases/tag/1.1.3 +[1.1.2]: https://github.com/seclai/seclai-python/releases/tag/1.1.2 +[1.1.1]: https://github.com/seclai/seclai-python/releases/tag/1.1.1 +[1.1.0]: https://github.com/seclai/seclai-python/releases/tag/1.1.0 +[1.0.6]: https://github.com/seclai/seclai-python/releases/tag/1.0.6 +[1.0.5]: https://github.com/seclai/seclai-python/releases/tag/1.0.5 +[1.0.4]: https://github.com/seclai/seclai-python/releases/tag/1.0.4 +[1.0.3]: https://github.com/seclai/seclai-python/releases/tag/1.0.3 +[1.0.2]: https://github.com/seclai/seclai-python/releases/tag/1.0.2 +[1.0.1]: https://github.com/seclai/seclai-python/releases/tag/1.0.1 +[1.0.0]: https://github.com/seclai/seclai-python/releases/tag/1.0.0 +[0.0.1]: https://github.com/seclai/seclai-python/releases/tag/0.0.1 diff --git a/README.md b/README.md index d2ce298..bd8d604 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,17 @@ https://seclai.github.io/seclai-python/latest/ ## Resources +### Identity + +```python +me = client.get_me() +print(me["account_id"]) +for org in me["organizations"]: + print(org["name"], org["account_id"]) + +# Act as an organization: Seclai(account_id=org["account_id"]) +``` + ### Agents ```python @@ -158,6 +169,11 @@ https://seclai.github.io/seclai-python/latest/ agents = client.list_agents(page=1, limit=20) agent = client.create_agent({"name": "My Agent", "description": "..."}) fetched = client.get_agent("agent_id") + +# Pause / resume — a disabled agent stops firing from every trigger path +callers = client.get_agent_callers("agent_id") # live agents calling this one +client.disable_agent("agent_id") # 409 if any caller above is still live +client.enable_agent("agent_id") updated = client.update_agent("agent_id", {"name": "Renamed"}) client.delete_agent("agent_id") @@ -499,9 +515,81 @@ prefs = client.list_organization_alert_preferences() client.update_organization_alert_preference("org_id", "anomaly", {"enabled": True}) ``` +### Agent email triggers + +```python +# Configure an EMAIL_RECEIVED trigger; omitted fields are left unchanged +config = client.set_email_trigger_config( + "agent_id", + "trigger_id", + { + "alias": "support", + "allowed_senders": ["example.com", "ops@partner.com"], + "ignore_auto_generated": True, # drop auto-replies to prevent loops + "require_sender_auth": True, # require SPF or DMARC + "queue_on_quota": False, # park over-rate mail instead of failing + }, +) +print(config["email_addresses"]) +``` + +### Agent email governance + +```python +# Recipients who opted out of this account's agent emails +opt_outs = client.list_agent_email_optouts(agent_id="agent_id", limit=50) +client.remove_agent_email_optout("optout_id") # opt them back in + +# Blocked inbound senders (owner/admin only) +blocked = client.list_blocked_email_senders(limit=50) +client.block_email_sender({"sender_email": "spam.example.com", "match_type": "domain"}) +client.unblock_email_sender("blocked_id") + +# Auto-block on a governance BLOCK: "disabled" | "input" | "input_and_output" +client.set_auto_block_mode({"mode": "input_and_output"}) + +# Inbound mail discarded before running an agent +rejections = client.list_inbound_email_rejections(agent_id="agent_id") + +# Account-wide overload circuit breaker +status = client.get_inbound_email_status() # {"paused": ..., "queued_backlog": ...} +client.cancel_queued_email_runs() # fail all QUEUED (over-quota parked) runs +client.resume_inbound_email() # one-shot; re-arms if still overloaded +``` + +### Email domains + +Send and receive agent email on your own domain instead of the shared +`agent.seclai.com`. Requires a user-bound credential; mutations require an +account owner/admin. + +```python +listing = client.list_email_domains() + +vanity = client.add_email_domain({"kind": "vanity", "value": "acme"}) +custom = client.add_email_domain( + {"kind": "custom", "value": "agent.mycompany.com", "delegated": True} +) + +# Publish custom["dns_records"], then check without waiting for the sweep +client.verify_email_domain(custom["id"]) + +client.set_primary_email_domain(custom["id"]) +client.use_shared_email_domain() # revert; domains stay configured & verified + +client.send_email_domain_test_email(custom["id"]) # always to the account owner +dmarc = client.get_dmarc_summary(custom["id"], days=30, top_sources=10) + +removed = client.remove_email_domain(custom["id"]) +print(removed.get("cleanup_note")) # set when the domain was Seclai-managed +``` + ### Models ```python +# Media-generation quality tiers (fast/balanced/thorough) and what each resolves to +tiers = client.get_generation_tiers() + alerts = client.list_model_alerts() client.mark_model_alert_read("alert_id") client.mark_all_model_alerts_read() @@ -523,6 +611,17 @@ results = client.search(query="quarterly report") filtered = client.search(query="my agent", entity_type="agent", limit=5) ``` +### Documentation search + +Results are global (not account-scoped); each carries a `doc_slug` plus an +optional `anchor` for building a `https://seclai.com/docs/[#]` link. + +```python +hits = client.search_docs("email triggers") # fast keyword match +deep = client.search_docs("how do I stop auto-reply loops", + mode="semantic", limit=5) # adds a highlight +``` + ### Top-level AI assistant ```python diff --git a/openapi/seclai.openapi.json b/openapi/seclai.openapi.json index fdb4658..6ad7ec9 100644 --- a/openapi/seclai.openapi.json +++ b/openapi/seclai.openapi.json @@ -52,6 +52,32 @@ "title": "AddConversationTurnRequest", "type": "object" }, + "AddEmailDomainRequest": { + "properties": { + "delegated": { + "default": false, + "title": "Delegated", + "type": "boolean" + }, + "kind": { + "description": "'vanity' or 'custom'", + "title": "Kind", + "type": "string" + }, + "value": { + "maxLength": 253, + "minLength": 1, + "title": "Value", + "type": "string" + } + }, + "required": [ + "kind", + "value" + ], + "title": "AddEmailDomainRequest", + "type": "object" + }, "AgentAttachmentRefsApiResponse": { "description": "Static attachment-reference contract for an agent.\n\nMirrors the MCP ``get_agent_attachment_references`` tool: returns\nwhat files (if any) an agent's templates expect on a run so API\nconsumers can stage uploads correctly before calling\n``POST /agents/{id}/runs``.", "properties": { @@ -71,6 +97,31 @@ "title": "AgentAttachmentRefsApiResponse", "type": "object" }, + "AgentCallerApiResponse": { + "description": "One agent that calls another (blocks disabling the callee while live).", + "properties": { + "disabled": { + "title": "Disabled", + "type": "boolean" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + } + }, + "required": [ + "id", + "name", + "disabled" + ], + "title": "AgentCallerApiResponse", + "type": "object" + }, "AgentDefinitionImportErrorResponse": { "description": "422 body for invalid `agent_definition` payloads.\n\nMirrors `AgentDefinitionImportError.to_response_dict`.", "properties": { @@ -151,6 +202,102 @@ "title": "AgentDefinitionResponse", "type": "object" }, + "AgentEmailOptOutListResponse": { + "description": "A page of agent-email opt-outs plus the total (for pagination).", + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/AgentEmailOptOutResponse" + }, + "title": "Items", + "type": "array" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "items", + "total" + ], + "title": "AgentEmailOptOutListResponse", + "type": "object" + }, + "AgentEmailOptOutResponse": { + "description": "A recipient's opt-out from an account's agent emails (one agent or all).", + "properties": { + "agent_id": { + "anyOf": [ + { + "format": "uuid", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Agent Id" + }, + "agent_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Agent Name" + }, + "comment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Comment" + }, + "created_at": { + "title": "Created At", + "type": "string" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reason" + }, + "recipient_email": { + "title": "Recipient Email", + "type": "string" + } + }, + "required": [ + "id", + "created_at", + "recipient_email", + "agent_id", + "agent_name", + "reason", + "comment" + ], + "title": "AgentEmailOptOutResponse", + "type": "object" + }, "AgentEvaluationTier": { "description": "Controls model selection for agent evaluation.\n\nLabels shown in UI:\n FAST \u2192 \"Fast and cheap\"\n BALANCED \u2192 \"Balanced speed and cost\"\n THOROUGH \u2192 \"Slow and thorough\"", "enum": [ @@ -540,7 +687,7 @@ "type": "null" } ], - "description": "MIME type of `output` \u2014 mirrors the terminal step's `output_content_type`. Consumers interpret `output` differently depending on this value: `application/vnd.seclai.manifest+json` is a multi-asset manifest with shape `{text, attachments: [{storage_key, mime, name, bytes}]}` \u2014 fetch each attachment via `GET /authenticated/storage-blobs/{storage_key}`. `text/plain` / `text/*` are free-form text. `application/json` is a JSON document. Null on runs that produced no terminal output or that pre-date this column.", + "description": "MIME type of `output` \u2014 mirrors the terminal step's `output_content_type`. Consumers interpret `output` differently depending on this value: `application/vnd.seclai.manifest+json` is a multi-asset manifest with shape `{text, attachments: [{storage_key, mime, name, bytes}]}` \u2014 fetch each attachment via `GET /api/v2/agent-runs/{run_id}/attachments/{attachment_id}`, where `attachment_id` is the URL-safe base64 of the attachment's `storage_key` (accepts an API key or OAuth token). `text/plain` / `text/*` are free-form text. `application/json` is a JSON document. Null on runs that produced no terminal output or that pre-date this column.", "title": "Output Content Type" }, "priority": { @@ -583,6 +730,18 @@ ], "description": "Step outputs and per-step timing/credits. Only included when requested.", "title": "Steps" + }, + "wait_ms": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Cumulative milliseconds the run was parked on standard-mode wait steps. Subtracted from active duration in run-detail and duration-stats responses, exactly like hitl_wait_ms. Priority waits block inline and are not counted here.", + "title": "Wait Ms" } }, "required": [ @@ -939,6 +1098,36 @@ "description": "Agent description.", "title": "Description" }, + "disabled": { + "default": false, + "description": "Whether the agent is paused (disabled).", + "title": "Disabled", + "type": "boolean" + }, + "disabled_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "ISO 8601 timestamp the agent was paused.", + "title": "Disabled At" + }, + "disabled_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Why the agent is paused: 'manual' or 'email_overload'.", + "title": "Disabled Reason" + }, "evaluation_mode": { "default": "eval_and_retry", "description": "Evaluation mode: output_expectation, eval_and_retry, or sample_and_flag.", @@ -1521,6 +1710,113 @@ "title": "AttachmentRefsSourceApiSummary", "type": "object" }, + "BlockEmailSenderRequest": { + "description": "Add one sender/domain to the account blocklist (shared REST request).", + "properties": { + "match_type": { + "default": "address", + "title": "Match Type", + "type": "string" + }, + "note": { + "anyOf": [ + { + "maxLength": 500, + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Note" + }, + "sender_email": { + "maxLength": 320, + "minLength": 3, + "title": "Sender Email", + "type": "string" + } + }, + "required": [ + "sender_email" + ], + "title": "BlockEmailSenderRequest", + "type": "object" + }, + "BlockedEmailSenderListResponse": { + "description": "A page of blocked senders + the account's auto-block mode.", + "properties": { + "auto_block_mode": { + "title": "Auto Block Mode", + "type": "string" + }, + "items": { + "items": { + "$ref": "#/components/schemas/BlockedEmailSenderResponse" + }, + "title": "Items", + "type": "array" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "items", + "total", + "auto_block_mode" + ], + "title": "BlockedEmailSenderListResponse", + "type": "object" + }, + "BlockedEmailSenderResponse": { + "description": "A single blocked inbound email sender.", + "properties": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "match_type": { + "title": "Match Type", + "type": "string" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Note" + }, + "sender_email": { + "title": "Sender Email", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "id", + "created_at", + "sender_email", + "match_type", + "source", + "note" + ], + "title": "BlockedEmailSenderResponse", + "type": "object" + }, "Body_upload_file_to_content_api_contents__source_connection_content_version__upload_post": { "properties": { "file": { @@ -1585,6 +1881,19 @@ "title": "Body_upload_file_to_source_api_sources__source_connection_id__upload_post", "type": "object" }, + "CancelQueuedRunsResponse": { + "properties": { + "cancelled": { + "title": "Cancelled", + "type": "integer" + } + }, + "required": [ + "cancelled" + ], + "title": "CancelQueuedRunsResponse", + "type": "object" + }, "ChangeStatusRequest": { "properties": { "note": { @@ -2424,6 +2733,21 @@ ], "description": "Index mode for custom_index sources: fast_and_cheap (default), balanced, slow_and_thorough, or custom." }, + "media_types": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Media kinds to extract from indexed content and embed as multi-modal KB chunks. Subset of ['images', 'video']. Only kinds the source's embedder can index are honored; unsupported values are dropped. Omit / [] for text-only.", + "title": "Media Types" + }, "name": { "description": "Source name.", "maxLength": 255, @@ -2506,24 +2830,13 @@ "title": "CreateSourceBody", "type": "object" }, - "EvaluationCriteriaResponse": { - "description": "Response schema for evaluation criteria.", + "DmarcFailingSourceResponse": { "properties": { - "account_id": { - "format": "uuid", - "title": "Account Id", - "type": "string" - }, - "agent_id": { - "format": "uuid", - "title": "Agent Id", - "type": "string" + "failed_count": { + "title": "Failed Count", + "type": "integer" }, - "created_at": { - "title": "Created At", - "type": "string" - }, - "description": { + "header_from": { "anyOf": [ { "type": "string" @@ -2532,29 +2845,86 @@ "type": "null" } ], - "title": "Description" - }, - "enabled": { - "title": "Enabled", - "type": "boolean" + "title": "Header From" }, - "evaluation_mode": { - "description": "Runtime behavior mode. output_expectation (manual validation), eval_and_retry (every run + retry), sample_and_flag (sampled monitoring).", - "title": "Evaluation Mode", + "source_ip": { + "title": "Source Ip", "type": "string" + } + }, + "required": [ + "source_ip", + "failed_count" + ], + "title": "DmarcFailingSourceResponse", + "type": "object" + }, + "DmarcSummaryResponse": { + "properties": { + "dispositions": { + "additionalProperties": { + "type": "integer" + }, + "title": "Dispositions", + "type": "object" }, - "evaluation_prompt": { + "failed_messages": { + "title": "Failed Messages", + "type": "integer" + }, + "monitored": { + "default": true, + "title": "Monitored", + "type": "boolean" + }, + "pass_rate": { "anyOf": [ { - "type": "string" + "type": "number" }, { "type": "null" } ], - "title": "Evaluation Prompt" + "title": "Pass Rate" }, - "evaluation_tier": { + "passed_messages": { + "title": "Passed Messages", + "type": "integer" + }, + "report_count": { + "title": "Report Count", + "type": "integer" + }, + "top_failing_sources": { + "items": { + "$ref": "#/components/schemas/DmarcFailingSourceResponse" + }, + "title": "Top Failing Sources", + "type": "array" + }, + "total_messages": { + "title": "Total Messages", + "type": "integer" + }, + "window_days": { + "title": "Window Days", + "type": "integer" + } + }, + "required": [ + "window_days", + "report_count", + "total_messages", + "passed_messages", + "failed_messages" + ], + "title": "DmarcSummaryResponse", + "type": "object" + }, + "DnsProviderResponse": { + "properties": { + "dashboard_url": { "anyOf": [ { "type": "string" @@ -2563,48 +2933,59 @@ "type": "null" } ], - "title": "Evaluation Tier" + "title": "Dashboard Url" }, - "expectation_config": { + "key": { + "title": "Key", + "type": "string" + }, + "mx_priority_separate": { + "default": true, + "title": "Mx Priority Separate", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "tips": { + "items": { + "type": "string" + }, + "title": "Tips", + "type": "array" + }, + "txt_quotes": { + "default": "strip", + "title": "Txt Quotes", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "title": "DnsProviderResponse", + "type": "object" + }, + "DnsRecordResponse": { + "properties": { + "detail": { "anyOf": [ { - "additionalProperties": true, - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Expectation Config" + "title": "Detail" }, - "id": { - "format": "uuid", - "title": "Id", + "key": { + "title": "Key", "type": "string" }, - "max_retries": { - "title": "Max Retries", - "type": "integer" - }, - "pass_threshold": { - "description": "Score cutoff for pass/fail, inclusive (0.0 to 1.0).", - "maximum": 1.0, - "minimum": 0.0, - "title": "Pass Threshold", - "type": "number" - }, - "result_summary": { - "additionalProperties": { - "type": "integer" - }, - "title": "Result Summary", - "type": "object" - }, - "retry_on_failure": { - "title": "Retry On Failure", - "type": "boolean" - }, - "step_id": { + "mx_host": { "anyOf": [ { "type": "string" @@ -2613,215 +2994,418 @@ "type": "null" } ], - "title": "Step Id" + "title": "Mx Host" }, - "updated_at": { - "title": "Updated At", + "mx_priority": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Mx Priority" + }, + "name": { + "title": "Name", + "type": "string" + }, + "ok": { + "title": "Ok", + "type": "boolean" + }, + "relative_name": { + "title": "Relative Name", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + }, + "value": { + "title": "Value", "type": "string" } }, "required": [ - "id", - "agent_id", - "account_id", - "description", - "evaluation_mode", - "enabled", - "step_id", - "expectation_config", - "max_retries", - "retry_on_failure", - "evaluation_prompt", - "pass_threshold", - "evaluation_tier", - "created_at", - "updated_at", - "result_summary" + "key", + "type", + "name", + "relative_name", + "value", + "ok" ], - "title": "EvaluationCriteriaResponse", + "title": "DnsRecordResponse", "type": "object" }, - "EvaluationResultListResponse": { - "description": "Paginated list of evaluation results.", + "EmailDomainResponse": { "properties": { - "data": { + "delegated": { + "default": false, + "title": "Delegated", + "type": "boolean" + }, + "dns_records": { "items": { - "$ref": "#/components/schemas/EvaluationResultResponse" + "$ref": "#/components/schemas/DnsRecordResponse" }, - "title": "Data", + "title": "Dns Records", "type": "array" }, - "limit": { - "title": "Limit", - "type": "integer" - }, - "page": { - "title": "Page", - "type": "integer" - }, - "total": { - "title": "Total", - "type": "integer" - } - }, - "required": [ - "data", - "total", - "page", - "limit" - ], - "title": "EvaluationResultListResponse", - "type": "object" - }, - "EvaluationResultResponse": { - "description": "Response schema for a single evaluation result.", - "properties": { - "agent_run_id": { - "format": "uuid", - "title": "Agent Run Id", + "domain": { + "title": "Domain", "type": "string" }, - "agent_step_run_id": { + "error_message": { "anyOf": [ { - "format": "uuid", "type": "string" }, { "type": "null" } ], - "title": "Agent Step Run Id" + "title": "Error Message" }, - "created_at": { - "title": "Created At", + "id": { + "format": "uuid", + "title": "Id", "type": "string" }, - "criteria_id": { - "format": "uuid", - "title": "Criteria Id", + "is_primary": { + "title": "Is Primary", + "type": "boolean" + }, + "kind": { + "title": "Kind", "type": "string" }, - "details": { + "last_checked_at": { "anyOf": [ { - "additionalProperties": true, - "type": "object" + "format": "date-time", + "type": "string" }, { "type": "null" } ], - "description": "Evaluation details including explanation and raw LLM response.", - "title": "Details" + "title": "Last Checked At" }, - "evaluated_at": { - "title": "Evaluated At", - "type": "string" + "provider": { + "anyOf": [ + { + "$ref": "#/components/schemas/DnsProviderResponse" + }, + { + "type": "null" + } + ] }, - "flagged": { - "description": "True when the result was flagged for human review.", - "title": "Flagged", + "regressing": { + "default": false, + "title": "Regressing", "type": "boolean" }, - "id": { - "format": "uuid", - "title": "Id", + "status": { + "title": "Status", "type": "string" }, - "retry_count": { - "title": "Retry Count", - "type": "integer" - }, - "retry_triggered": { - "title": "Retry Triggered", + "verified": { + "default": false, + "title": "Verified", "type": "boolean" }, - "score": { + "verified_at": { "anyOf": [ { - "type": "number" + "format": "date-time", + "type": "string" }, { "type": "null" } ], - "description": "LLM-assigned quality score between 0.0 (worst) and 1.0 (best).", - "title": "Score" + "title": "Verified At" }, - "status": { - "description": "Outcome status: pending, passed, failed, skipped, or error.", - "title": "Status", + "zone_apex": { + "default": "", + "title": "Zone Apex", "type": "string" } }, "required": [ "id", - "criteria_id", - "agent_run_id", - "agent_step_run_id", + "domain", + "kind", "status", - "retry_triggered", - "retry_count", - "flagged", - "evaluated_at", - "created_at" + "is_primary" ], - "title": "EvaluationResultResponse", + "title": "EmailDomainResponse", "type": "object" }, - "EvaluationResultSummaryResponse": { - "description": "Aggregated pass/fail/error counts and average score for a criteria.", + "EmailDomainsListResponse": { "properties": { - "average_score": { + "can_add_custom": { + "default": false, + "title": "Can Add Custom", + "type": "boolean" + }, + "can_add_vanity": { + "default": false, + "title": "Can Add Vanity", + "type": "boolean" + }, + "custom_plan_names": { + "items": { + "type": "string" + }, + "title": "Custom Plan Names", + "type": "array" + }, + "domains": { + "items": { + "$ref": "#/components/schemas/EmailDomainResponse" + }, + "title": "Domains", + "type": "array" + }, + "has_custom": { + "default": false, + "title": "Has Custom", + "type": "boolean" + }, + "has_vanity": { + "default": false, + "title": "Has Vanity", + "type": "boolean" + }, + "vanity_plan_names": { + "items": { + "type": "string" + }, + "title": "Vanity Plan Names", + "type": "array" + } + }, + "title": "EmailDomainsListResponse", + "type": "object" + }, + "EmailTriggerConfigResponse": { + "description": "An EMAIL_RECEIVED trigger's resolved address(es) and config.", + "properties": { + "agent_id": { + "format": "uuid", + "title": "Agent Id", + "type": "string" + }, + "email_addresses": { + "items": { + "type": "string" + }, + "title": "Email Addresses", + "type": "array" + }, + "email_alias": { "anyOf": [ { - "type": "number" + "type": "string" }, { "type": "null" } ], - "description": "Mean score across all evaluated results, or null if none.", - "title": "Average Score" + "title": "Email Alias" }, - "error": { - "title": "Error", - "type": "integer" + "email_allowed_senders": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Email Allowed Senders" }, - "failed": { - "title": "Failed", - "type": "integer" + "email_ignore_auto_generated": { + "default": true, + "title": "Email Ignore Auto Generated", + "type": "boolean" }, - "flagged": { - "title": "Flagged", - "type": "integer" + "email_queue_on_quota": { + "default": false, + "title": "Email Queue On Quota", + "type": "boolean" }, - "passed": { - "title": "Passed", - "type": "integer" + "email_require_sender_auth": { + "default": true, + "title": "Email Require Sender Auth", + "type": "boolean" }, - "total": { - "title": "Total", + "trigger_id": { + "format": "uuid", + "title": "Trigger Id", + "type": "string" + }, + "trigger_type": { + "title": "Trigger Type", + "type": "string" + } + }, + "required": [ + "trigger_id", + "agent_id", + "trigger_type" + ], + "title": "EmailTriggerConfigResponse", + "type": "object" + }, + "EvaluationCriteriaResponse": { + "description": "Response schema for evaluation criteria.", + "properties": { + "account_id": { + "format": "uuid", + "title": "Account Id", + "type": "string" + }, + "agent_id": { + "format": "uuid", + "title": "Agent Id", + "type": "string" + }, + "created_at": { + "title": "Created At", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "enabled": { + "title": "Enabled", + "type": "boolean" + }, + "evaluation_mode": { + "description": "Runtime behavior mode. output_expectation (manual validation), eval_and_retry (every run + retry), sample_and_flag (sampled monitoring).", + "title": "Evaluation Mode", + "type": "string" + }, + "evaluation_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Evaluation Prompt" + }, + "evaluation_tier": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Evaluation Tier" + }, + "expectation_config": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Expectation Config" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "max_retries": { + "title": "Max Retries", "type": "integer" + }, + "pass_threshold": { + "description": "Score cutoff for pass/fail, inclusive (0.0 to 1.0).", + "maximum": 1.0, + "minimum": 0.0, + "title": "Pass Threshold", + "type": "number" + }, + "result_summary": { + "additionalProperties": { + "type": "integer" + }, + "title": "Result Summary", + "type": "object" + }, + "retry_on_failure": { + "title": "Retry On Failure", + "type": "boolean" + }, + "step_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Step Id" + }, + "updated_at": { + "title": "Updated At", + "type": "string" } }, "required": [ - "total", - "passed", - "failed", - "error", - "flagged" + "id", + "agent_id", + "account_id", + "description", + "evaluation_mode", + "enabled", + "step_id", + "expectation_config", + "max_retries", + "retry_on_failure", + "evaluation_prompt", + "pass_threshold", + "evaluation_tier", + "created_at", + "updated_at", + "result_summary" ], - "title": "EvaluationResultSummaryResponse", + "title": "EvaluationCriteriaResponse", "type": "object" }, - "EvaluationResultWithCriteriaListResponse": { - "description": "Paginated list of evaluation results with criteria context.", + "EvaluationResultListResponse": { + "description": "Paginated list of evaluation results.", "properties": { "data": { "items": { - "$ref": "#/components/schemas/EvaluationResultWithCriteriaResponse" + "$ref": "#/components/schemas/EvaluationResultResponse" }, "title": "Data", "type": "array" @@ -2845,11 +3429,11 @@ "page", "limit" ], - "title": "EvaluationResultWithCriteriaListResponse", + "title": "EvaluationResultListResponse", "type": "object" }, - "EvaluationResultWithCriteriaResponse": { - "description": "Evaluation result including criteria context for aggregated listing.", + "EvaluationResultResponse": { + "description": "Response schema for a single evaluation result.", "properties": { "agent_run_id": { "format": "uuid", @@ -2872,17 +3456,6 @@ "title": "Created At", "type": "string" }, - "criteria_description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Criteria Description" - }, "criteria_id": { "format": "uuid", "title": "Criteria Id", @@ -2939,14 +3512,200 @@ "description": "Outcome status: pending, passed, failed, skipped, or error.", "title": "Status", "type": "string" - }, - "step_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + } + }, + "required": [ + "id", + "criteria_id", + "agent_run_id", + "agent_step_run_id", + "status", + "retry_triggered", + "retry_count", + "flagged", + "evaluated_at", + "created_at" + ], + "title": "EvaluationResultResponse", + "type": "object" + }, + "EvaluationResultSummaryResponse": { + "description": "Aggregated pass/fail/error counts and average score for a criteria.", + "properties": { + "average_score": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Mean score across all evaluated results, or null if none.", + "title": "Average Score" + }, + "error": { + "title": "Error", + "type": "integer" + }, + "failed": { + "title": "Failed", + "type": "integer" + }, + "flagged": { + "title": "Flagged", + "type": "integer" + }, + "passed": { + "title": "Passed", + "type": "integer" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "total", + "passed", + "failed", + "error", + "flagged" + ], + "title": "EvaluationResultSummaryResponse", + "type": "object" + }, + "EvaluationResultWithCriteriaListResponse": { + "description": "Paginated list of evaluation results with criteria context.", + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/EvaluationResultWithCriteriaResponse" + }, + "title": "Data", + "type": "array" + }, + "limit": { + "title": "Limit", + "type": "integer" + }, + "page": { + "title": "Page", + "type": "integer" + }, + "total": { + "title": "Total", + "type": "integer" + } + }, + "required": [ + "data", + "total", + "page", + "limit" + ], + "title": "EvaluationResultWithCriteriaListResponse", + "type": "object" + }, + "EvaluationResultWithCriteriaResponse": { + "description": "Evaluation result including criteria context for aggregated listing.", + "properties": { + "agent_run_id": { + "format": "uuid", + "title": "Agent Run Id", + "type": "string" + }, + "agent_step_run_id": { + "anyOf": [ + { + "format": "uuid", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Agent Step Run Id" + }, + "created_at": { + "title": "Created At", + "type": "string" + }, + "criteria_description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Criteria Description" + }, + "criteria_id": { + "format": "uuid", + "title": "Criteria Id", + "type": "string" + }, + "details": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Evaluation details including explanation and raw LLM response.", + "title": "Details" + }, + "evaluated_at": { + "title": "Evaluated At", + "type": "string" + }, + "flagged": { + "description": "True when the result was flagged for human review.", + "title": "Flagged", + "type": "boolean" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "retry_count": { + "title": "Retry Count", + "type": "integer" + }, + "retry_triggered": { + "title": "Retry Triggered", + "type": "boolean" + }, + "score": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "LLM-assigned quality score between 0.0 (worst) and 1.0 (best).", + "title": "Score" + }, + "status": { + "description": "Outcome status: pending, passed, failed, skipped, or error.", + "title": "Status", + "type": "string" + }, + "step_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } ], "title": "Step Id" @@ -3100,6 +3859,12 @@ "description": "Error message if failed.", "title": "Error" }, + "needs_clarification": { + "default": false, + "description": "True when the action paused for a clarifying question rather than failing. The created resource is kept and the question is in ``description``/``error``.", + "title": "Needs Clarification", + "type": "boolean" + }, "resource_id": { "anyOf": [ { @@ -3256,6 +4021,18 @@ "title": "Example Prompts", "type": "array" }, + "intent_assessment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "How the assistant interpreted the request: 'clear' when steps were generated, or an ask-path value (e.g. 'ambiguous_output', 'cannot_build') when it returned no steps and put a clarifying question or blocker in `note`. Mirrors the MCP surface so callers can distinguish a clarification pause from a hard failure.", + "title": "Intent Assessment" + }, "note": { "description": "AI explanation of the proposed workflow.", "title": "Note", @@ -3570,41 +4347,54 @@ "title": "ImportSkipResponse", "type": "object" }, - "InlineTextReplaceRequest": { - "description": "Request model for inline text content replacement.", + "InboundEmailRejectionResponse": { + "description": "An inbound email that was discarded without running an agent.", "properties": { - "content_type": { + "agent_id": { "anyOf": [ { + "format": "uuid", "type": "string" }, { "type": "null" } ], - "default": "text/plain", - "description": "MIME type for the text content", - "title": "Content Type" + "title": "Agent Id" }, - "metadata": { + "created_at": { + "title": "Created At", + "type": "string" + }, + "id": { + "format": "uuid", + "title": "Id", + "type": "string" + }, + "message_id": { "anyOf": [ { - "additionalProperties": true, - "type": "object" + "type": "string" }, { "type": "null" } ], - "description": "Optional metadata object", - "title": "Metadata" + "title": "Message Id" }, - "text": { - "description": "Text content to upload", - "title": "Text", + "reason": { + "title": "Reason", "type": "string" }, - "title": { + "recipient": { + "title": "Recipient", + "type": "string" + }, + "sender": { + "title": "Sender", + "type": "string" + }, + "sender_ip": { "anyOf": [ { "type": "string" @@ -3613,20 +4403,9 @@ "type": "null" } ], - "description": "Optional title", - "title": "Title" - } - }, - "required": [ - "text" - ], - "title": "InlineTextReplaceRequest", - "type": "object" - }, - "InlineTextUploadRequest": { - "description": "Request model for inline text uploads.", - "properties": { - "content_type": { + "title": "Sender Ip" + }, + "subject": { "anyOf": [ { "type": "string" @@ -3635,9 +4414,109 @@ "type": "null" } ], - "default": "text/plain", - "description": "MIME type for the text content", - "title": "Content Type" + "title": "Subject" + } + }, + "required": [ + "id", + "created_at", + "recipient", + "sender", + "sender_ip", + "reason", + "message_id", + "agent_id", + "subject" + ], + "title": "InboundEmailRejectionResponse", + "type": "object" + }, + "InboundEmailStatusResponse": { + "properties": { + "paused": { + "title": "Paused", + "type": "boolean" + }, + "queued_backlog": { + "title": "Queued Backlog", + "type": "integer" + } + }, + "required": [ + "paused", + "queued_backlog" + ], + "title": "InboundEmailStatusResponse", + "type": "object" + }, + "InlineTextReplaceRequest": { + "description": "Request model for inline text content replacement.", + "properties": { + "content_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "text/plain", + "description": "MIME type for the text content", + "title": "Content Type" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Optional metadata object", + "title": "Metadata" + }, + "text": { + "description": "Text content to upload", + "title": "Text", + "type": "string" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optional title", + "title": "Title" + } + }, + "required": [ + "text" + ], + "title": "InlineTextReplaceRequest", + "type": "object" + }, + "InlineTextUploadRequest": { + "description": "Request model for inline text uploads.", + "properties": { + "content_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "text/plain", + "description": "MIME type for the text content", + "title": "Content Type" }, "metadata": { "anyOf": [ @@ -4427,7 +5306,9 @@ "processing", "completed", "failed", - "waiting_human" + "queued", + "waiting_human", + "waiting_scheduled" ], "title": "PendingProcessingCompletedFailedStatus", "type": "string" @@ -4716,6 +5597,66 @@ "title": "ProposedPolicyActionResponse", "type": "object" }, + "RemoveEmailDomainResponse": { + "properties": { + "cleanup_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cleanup Note" + }, + "removed": { + "default": true, + "title": "Removed", + "type": "boolean" + } + }, + "title": "RemoveEmailDomainResponse", + "type": "object" + }, + "ResumeInboundResponse": { + "properties": { + "resumed": { + "title": "Resumed", + "type": "boolean" + } + }, + "required": [ + "resumed" + ], + "title": "ResumeInboundResponse", + "type": "object" + }, + "SendTestEmailResponse": { + "properties": { + "sent": { + "default": true, + "title": "Sent", + "type": "boolean" + } + }, + "title": "SendTestEmailResponse", + "type": "object" + }, + "SetAutoBlockModeRequest": { + "description": "Set the account's governance auto-block mode (shared REST request).", + "properties": { + "mode": { + "title": "Mode", + "type": "string" + } + }, + "required": [ + "mode" + ], + "title": "SetAutoBlockModeRequest", + "type": "object" + }, "SolutionSourceConnectionResponse": { "properties": { "id": { @@ -5178,6 +6119,14 @@ ], "description": "Index mode for custom_index sources: fast_and_cheap, balanced, slow_and_thorough, or custom." }, + "media_types": { + "description": "Media kinds extracted from indexed content and embedded as multi-modal KB chunks (subset of ['images', 'video']). Empty = text-only.", + "items": { + "type": "string" + }, + "title": "Media Types", + "type": "array" + }, "name": { "description": "Name of the source connection.", "title": "Name", @@ -6072,6 +7021,21 @@ "UpdateSourceBody": { "description": "Request body for updating a content source.", "properties": { + "media_types": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Media kinds to extract from indexed content and embed as multi-modal KB chunks. Subset of ['images', 'video']. Only kinds the source's embedder can index are honored; unsupported values are dropped. [] disables media extraction (text-only).", + "title": "Media Types" + }, "name": { "anyOf": [ { @@ -6650,7 +7614,7 @@ }, "trigger_type": { "default": "dynamic_input", - "description": "Trigger type: dynamic_input, template_input, schedule, new_content.", + "description": "Trigger type: dynamic_input, template_input, schedule, new_content, email_received.", "title": "Trigger Type", "type": "string" } @@ -6688,6 +7652,77 @@ "title": "GovernancePolicyRefResponse", "type": "object" }, + "routers__api__agents__SetEmailTriggerConfigRequest": { + "description": "Alias and/or sender allowlist for an EMAIL_RECEIVED trigger.\n\nA field omitted is left unchanged; passing ``null`` (or ``\"\"`` for\n``alias``) clears it.", + "properties": { + "alias": { + "anyOf": [ + { + "maxLength": 32, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Custom alias for the address `.@agent.seclai.com` (alphanumeric plus '+', '.', '-'; 1\u201332 chars; not starting/ending with '+', '.', '-'; not UUID-shaped). Pass null/empty to clear.", + "title": "Alias" + }, + "allowed_senders": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Allowlist of full sender addresses and/or bare domains (a bare domain also matches sub-domains). Empty/null accepts any sender.", + "title": "Allowed Senders" + }, + "ignore_auto_generated": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "When true (default for new triggers), machine-generated inbound mail (auto-replies, bulk/list mail, bounces) is dropped before a run to prevent auto-reply loops. Set false to process automated mail.", + "title": "Ignore Auto Generated" + }, + "queue_on_quota": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "When true (default false), inbound mail that exceeds the account's hourly email-trigger rate is parked in a QUEUED run and drained later by the catch-up sweep instead of being failed; when false, over-rate mail fails immediately.", + "title": "Queue On Quota" + }, + "require_sender_auth": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "When true (default for new triggers), the envelope sender must pass SPF or DMARC even on an open inbox (no allowlist); unauthenticated, spoofable mail is rejected. Set false to accept fully unauthenticated mail on an open inbox.", + "title": "Require Sender Auth" + } + }, + "title": "SetEmailTriggerConfigRequest", + "type": "object" + }, "routers__api__agents__UpdateAgentRequest": { "properties": { "agent_definition": { @@ -8342,10 +9377,59 @@ ], "title": "Family Generation" }, + "generation_credits_per_unit": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Per-unit credit cost for a dedicated media-generation model, in the unit named by ``generation_params.pricing_unit`` (per image / per second / per character / per output token). Multiply by the produced unit count (images, seconds, characters) for the run cost. None for token-billed (non-generation) models.", + "title": "Generation Credits Per Unit" + }, + "generation_params": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "description": "Media-generation descriptor (modality, pricing_unit, and modality-specific constraints). NULL for text LLMs; present for image/audio/video generation models. See schemas.generation_params.", + "title": "Generation Params" + }, + "generation_unit_label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Human suffix for the per-unit generation rate (e.g. ``/image``, ``/second``, ``/1k chars``, ``/1k tokens``) \u2014 single-sourced from the pricing unit so clients render cost without re-deriving the mapping. None for non-generation models. Char/token rates are shown per 1,000 (the ``/1k \u2026`` suffix), so scale ``generation_credits_per_unit`` accordingly for those units.", + "title": "Generation Unit Label" + }, "id": { "title": "Id", "type": "string" }, + "image_generation_tool_credits_per_image": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Per-image credit cost of using the built-in image_generation tool (it runs gpt-image-1). Set only for models that actually support the tool (tool-use capable); None otherwise.", + "title": "Image Generation Tool Credits Per Image" + }, "input_1h_cache_write_credits_per_1000_tokens": { "anyOf": [ { @@ -8860,7 +9944,7 @@ ] }, "post": { - "description": "Create a new agent.\n\nTrigger types:\n- `dynamic_input`: triggered via API with user-provided input\n- `template_input`: triggered via API with a predefined template\n- `schedule`: triggered on a schedule\n- `new_content`: triggered when new content arrives\n\nTemplates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline`\n\nImporting an existing agent:\n- Pass `agent_definition` with the JSON shape produced by `GET /agents/{id}/export`. The full extras suite (alert_configs, evaluation_criteria, governance_policies, schedules, solutions) is applied; items that don't resolve in this account are reported in the response's `import_warnings` array.\n- Use `POST /agents/preview-import` first to surface `unresolved_refs` (workflow refs to KBs, memory banks, source connections, sub-agents that don't exist here). Then pass `entity_remap: {source_uuid: target_uuid}` on this call to substitute them before save.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Agent is created in the caller's account.", + "description": "Create a new agent.\n\nTrigger types:\n- `dynamic_input`: triggered via API with user-provided input\n- `template_input`: triggered via API with a predefined template\n- `schedule`: triggered on a schedule\n- `new_content`: triggered when new content arrives\n- `email_received`: a virtual email inbox; runs when mail arrives at the agent's address. Configure the alias/allowlist with `PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config`.\n\nTemplates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline`\n\nImporting an existing agent:\n- Pass `agent_definition` with the JSON shape produced by `GET /agents/{id}/export`. The full extras suite (alert_configs, evaluation_criteria, governance_policies, schedules, solutions) is applied; items that don't resolve in this account are reported in the response's `import_warnings` array.\n- Use `POST /agents/preview-import` first to surface `unresolved_refs` (workflow refs to KBs, memory banks, source connections, sub-agents that don't exist here). Then pass `entity_remap: {source_uuid: target_uuid}` on this call to substitute them before save.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Agent is created in the caller's account.", "operationId": "create_agent_api_agents_post", "parameters": [ { @@ -8878,97 +9962,1052 @@ "required": true }, "responses": { - "201": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentSummaryResponse" + } + } + }, + "description": "Successful Response" + }, + "402": { + "description": "Plan limit exceeded \u2014 the account has reached the maximum number of agents allowed by its subscription plan." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" + } + } + }, + "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." + } + }, + "summary": "Create an agent", + "tags": [ + "agents" + ] + } + }, + "/agents/agent-email-optouts": { + "get": { + "description": "List recipients who have opted out of this account's agent emails (filter to one agent via `agent_id`; account-wide opt-outs always apply). Paginated via `limit`/`offset`; returns the page plus the `total` count.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's account.", + "operationId": "list_agent_email_optouts_api_api_agents_agent_email_optouts_get", + "parameters": [ + { + "description": "Filter to one agent (account-wide opt-outs still apply)", + "in": "query", + "name": "agent_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter to one agent (account-wide opt-outs still apply)", + "title": "Agent Id" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentEmailOptOutListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List agent-email opt-outs", + "tags": [ + "agents" + ] + } + }, + "/agents/agent-email-optouts/{optout_id}": { + "delete": { + "description": "Revoke an opt-out (opt the recipient back in to agent emails).\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; the opt-out must belong to the key's account.", + "operationId": "remove_agent_email_optout_api_api_agents_agent_email_optouts__optout_id__delete", + "parameters": [ + { + "in": "path", + "name": "optout_id", + "required": true, + "schema": { + "title": "Optout Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Revoke an agent-email opt-out", + "tags": [ + "agents" + ] + } + }, + "/agents/blocked-email-senders": { + "get": { + "description": "List the account's blocked inbound email senders (newest first, paginated via `limit`/`offset`) plus the governance `auto_block_mode`.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's account.", + "operationId": "list_blocked_email_senders_api_api_agents_blocked_email_senders_get", + "parameters": [ + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockedEmailSenderListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List blocked inbound email senders + the auto-block mode", + "tags": [ + "agents" + ] + }, + "post": { + "description": "Add a sender address or a whole domain to the account blocklist (idempotent; `match_type` is `address` (default) or `domain`).\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped to the key's account.", + "operationId": "block_email_sender_api_api_agents_blocked_email_senders_post", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockEmailSenderRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockedEmailSenderResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Block an inbound email sender or domain", + "tags": [ + "agents" + ] + } + }, + "/agents/blocked-email-senders/mode": { + "put": { + "description": "Set whether a governance BLOCK on an authenticated inbound email sender auto-adds them to the blocklist (`disabled`, `input`, or `input_and_output`); returns the updated list.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped to the key's account.", + "operationId": "set_auto_block_mode_api_api_agents_blocked_email_senders_mode_put", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetAutoBlockModeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockedEmailSenderListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Set the governance auto-block mode", + "tags": [ + "agents" + ] + } + }, + "/agents/blocked-email-senders/{blocked_id}": { + "delete": { + "description": "Remove a blocked sender by id.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; the blocked sender must belong to the key's account.", + "operationId": "unblock_email_sender_api_api_agents_blocked_email_senders__blocked_id__delete", + "parameters": [ + { + "in": "path", + "name": "blocked_id", + "required": true, + "schema": { + "title": "Blocked Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Unblock an inbound email sender", + "tags": [ + "agents" + ] + } + }, + "/agents/evaluation-criteria/{criteria_id}": { + "delete": { + "description": "Delete an evaluation criteria and all associated evaluation results.\n\nThis action is permanent and cannot be undone. All historical result\nrecords tied to the criteria are removed as well.", + "operationId": "delete_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__delete", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Delete Evaluation Criteria", + "tags": [ + "agent-evaluations" + ] + }, + "get": { + "description": "Get a single evaluation criteria by ID.\n\nReturns the full criteria configuration including type-specific fields,\ncurrent enabled state, and a summary of recent evaluation results.", + "operationId": "get_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__get", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationCriteriaResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Evaluation Criteria", + "tags": [ + "agent-evaluations" + ] + }, + "patch": { + "description": "Update an evaluation criteria.\n\nAccepts a partial update \u2013 only the fields included in the request body\nare modified. Use this to toggle enabled/disabled, change the\nevaluation prompt, adjust retries, or rename the criteria.", + "operationId": "update_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__patch", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEvaluationCriteriaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationCriteriaResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Update Evaluation Criteria", + "tags": [ + "agent-evaluations" + ] + } + }, + "/agents/evaluation-criteria/{criteria_id}/compatible-runs": { + "get": { + "description": "List agent runs that have a completed step matching the criteria's target step.\n\nReturns runs whose step output can be used for testing or replaying the\nevaluation criteria. Results are ordered newest-first and paginated.", + "operationId": "list_compatible_runs_api_agents_evaluation_criteria__criteria_id__compatible_runs_get", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "minimum": 1, + "title": "Page", + "type": "integer" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 20, + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "in": "query", + "name": "started_after", + "required": false, + "schema": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Started After" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompatibleRunListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Compatible Runs", + "tags": [ + "agent-evaluations" + ] + } + }, + "/agents/evaluation-criteria/{criteria_id}/results": { + "get": { + "description": "List evaluation results for a criteria with optional filtering.\n\nSupports filtering by status (pending, passed, failed, skipped, error),\nflagged-only mode, and an optional time range. Results are paginated\nwith configurable page size.", + "operationId": "list_evaluation_results_api_agents_evaluation_criteria__criteria_id__results_get", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + { + "in": "query", + "name": "flagged_only", + "required": false, + "schema": { + "default": false, + "title": "Flagged Only", + "type": "boolean" + } + }, + { + "in": "query", + "name": "time_from", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time From" + } + }, + { + "in": "query", + "name": "time_to", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time To" + } + }, + { + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "minimum": 1, + "title": "Page", + "type": "integer" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 20, + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationResultListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List Evaluation Results", + "tags": [ + "agent-evaluations" + ] + }, + "post": { + "description": "Record an evaluation result for a criteria.\n\nUse this endpoint to push results from external test harnesses, CI/CD\npipelines, or custom evaluation logic. Each result is linked to an\nagent run and optionally a specific step run.", + "operationId": "create_evaluation_result_api_agents_evaluation_criteria__criteria_id__results_post", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEvaluationResultRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationResultResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Create Evaluation Result", + "tags": [ + "agent-evaluations" + ] + } + }, + "/agents/evaluation-criteria/{criteria_id}/summary": { + "get": { + "description": "Get an aggregated summary of evaluation results for a criteria.\n\nReturns total, passed, failed, error, and flagged counts alongside the\naverage score, giving a quick overview of how the criteria is performing.", + "operationId": "get_evaluation_summary_api_agents_evaluation_criteria__criteria_id__summary_get", + "parameters": [ + { + "in": "path", + "name": "criteria_id", + "required": true, + "schema": { + "title": "Criteria Id", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationResultSummaryResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Evaluation Summary", + "tags": [ + "agent-evaluations" + ] + } + }, + "/agents/evaluation-results/non-manual-summary": { + "get": { + "description": "Get account-level evaluation summary for API key clients.\n\nReturns aggregated pass/fail/flagged counts and pass rates for each evaluation mode (eval_and_retry, sample_and_flag).", + "operationId": "get_non_manual_evaluation_summary_api_agents_evaluation_results_non_manual_summary_get", + "parameters": [ + { + "in": "query", + "name": "days", + "required": false, + "schema": { + "default": 30, + "maximum": 730, + "minimum": 1, + "title": "Days", + "type": "integer" + } + }, + { + "in": "query", + "name": "start_date", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Start Date" + } + }, + { + "in": "query", + "name": "end_date", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "End Date" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/schemas__v1__agent_evaluations__NonManualEvaluationSummaryResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get Non Manual Evaluation Summary", + "tags": [ + "agent-evaluations" + ] + } + }, + "/agents/inbound-email-rejections": { + "get": { + "description": "List recent inbound emails that were quietly discarded before running an agent (unauthorized sender, unknown alias, spam/virus, flood-shed) for this account's agents.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's account.", + "operationId": "list_inbound_email_rejections_api_api_agents_inbound_email_rejections_get", + "parameters": [ + { + "description": "Filter to a single agent's rejections", + "in": "query", + "name": "agent_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter to a single agent's rejections", + "title": "Agent Id" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/InboundEmailRejectionResponse" + }, + "title": "Response List Inbound Email Rejections Api Api Agents Inbound Email Rejections Get", + "type": "array" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List discarded inbound emails", + "tags": [ + "agents" + ] + } + }, + "/agents/inbound-email-status": { + "get": { + "description": "Whether the account-wide overload circuit breaker has currently paused new inbound email (all inbound turned away until the queued backlog drains), plus the size of the QUEUED (over-quota parked) run backlog.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's account.", + "operationId": "get_inbound_email_status_api_api_agents_inbound_email_status_get", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentSummaryResponse" + "$ref": "#/components/schemas/InboundEmailStatusResponse" } } }, "description": "Successful Response" - }, - "402": { - "description": "Plan limit exceeded \u2014 the account has reached the maximum number of agents allowed by its subscription plan." - }, - "422": { + } + }, + "summary": "Account inbound-email overload status", + "tags": [ + "agents" + ] + } + }, + "/agents/inbound-email-status/cancel-queued": { + "post": { + "description": "Fail all of the account's QUEUED (over-quota parked) inbound-email runs at once. A queued run consumed no quota or credits at queue time, so this merely fails them. Returns the count cancelled.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped to the key's account.", + "operationId": "cancel_queued_email_runs_api_api_agents_inbound_email_status_cancel_queued_post", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" + "$ref": "#/components/schemas/CancelQueuedRunsResponse" } } }, - "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." + "description": "Successful Response" } }, - "summary": "Create an agent", + "summary": "Cancel all queued inbound-email runs", "tags": [ "agents" ] } }, - "/agents/evaluation-criteria/{criteria_id}": { - "delete": { - "description": "Delete an evaluation criteria and all associated evaluation results.\n\nThis action is permanent and cannot be undone. All historical result\nrecords tied to the criteria are removed as well.", - "operationId": "delete_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__delete", + "/agents/inbound-email-status/resume": { + "post": { + "description": "Manually lift the account-wide inbound pause. If the queued backlog is still above the ceiling the breaker re-arms on the next evaluation \u2014 this is a one-shot override, not a permanent disable.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped to the key's account.", + "operationId": "resume_inbound_email_api_api_agents_inbound_email_status_resume_post", "parameters": [ - { - "in": "path", - "name": "criteria_id", - "required": true, - "schema": { - "title": "Criteria Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } ], "responses": { - "204": { - "description": "Successful Response" - }, - "422": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ResumeInboundResponse" } } }, - "description": "Validation Error" + "description": "Successful Response" } }, - "summary": "Delete Evaluation Criteria", + "summary": "Manually resume paused inbound email", "tags": [ - "agent-evaluations" + "agents" ] - }, - "get": { - "description": "Get a single evaluation criteria by ID.\n\nReturns the full criteria configuration including type-specific fields,\ncurrent enabled state, and a summary of recent evaluation results.", - "operationId": "get_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__get", + } + }, + "/agents/preview-import": { + "post": { + "description": "Validate an `agent_definition` payload (the same shape produced by `GET /api/agents/{agent_id}/export`) without creating or modifying any agent. On success returns a summary the client can show before commit (counts of steps, schedules, alert configs, evaluation criteria, governance policies). On failure returns the same 422 body shape used by `POST /api/agents` and `PUT /api/agents/{id}` so callers can render line/column-anchored errors.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. No DB writes.", + "operationId": "preview_import_agent_api_agents_preview_import_post", "parameters": [ - { - "in": "path", - "name": "criteria_id", - "required": true, - "schema": { - "title": "Criteria Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__agents__AgentImportPreviewRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationCriteriaResponse" + "$ref": "#/components/schemas/routers__api__agents__AgentImportPreviewResponse" } } }, @@ -8978,31 +11017,24 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" } } }, - "description": "Validation Error" + "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." } }, - "summary": "Get Evaluation Criteria", + "summary": "Preview an agent_definition import", "tags": [ - "agent-evaluations" + "agents" ] - }, - "patch": { - "description": "Update an evaluation criteria.\n\nAccepts a partial update \u2013 only the fields included in the request body\nare modified. Use this to toggle enabled/disabled, change the\nevaluation prompt, adjust retries, or rename the criteria.", - "operationId": "update_evaluation_criteria_api_agents_evaluation_criteria__criteria_id__patch", + } + }, + "/agents/runs/search": { + "post": { + "description": "Search agent traces using semantic similarity.\n\nFinds step-run outputs that are most semantically similar to the query.\nResults include the matching text, agent/step metadata, and a similarity score.\n\nAgent traces are automatically indexed when runs complete. The first 7 days of storage are free; extended retention is billed.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Searches only within your account's traces.", + "operationId": "search_agent_runs_api_agents_runs_search_post", "parameters": [ - { - "in": "path", - "name": "criteria_id", - "required": true, - "schema": { - "title": "Criteria Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9011,7 +11043,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateEvaluationCriteriaRequest" + "$ref": "#/components/schemas/routers__api__agents__AgentTraceSearchRequest" } } }, @@ -9022,7 +11054,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationCriteriaResponse" + "$ref": "#/components/schemas/AgentTraceSearchResponse" } } }, @@ -9039,66 +11071,26 @@ "description": "Validation Error" } }, - "summary": "Update Evaluation Criteria", + "summary": "Search agent traces", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/evaluation-criteria/{criteria_id}/compatible-runs": { - "get": { - "description": "List agent runs that have a completed step matching the criteria's target step.\n\nReturns runs whose step output can be used for testing or replaying the\nevaluation criteria. Results are ordered newest-first and paginated.", - "operationId": "list_compatible_runs_api_agents_evaluation_criteria__criteria_id__compatible_runs_get", + "/agents/runs/{run_id}": { + "delete": { + "description": "Cancel a running agent run.\n\nIf the run is already in a terminal state (`completed` or `failed`), cancellation will be rejected.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only cancel runs belonging to your account.", + "operationId": "delete_agent_run_api_agents_runs__run_id__delete", "parameters": [ { "in": "path", - "name": "criteria_id", + "name": "run_id", "required": true, "schema": { - "title": "Criteria Id", + "title": "Run Id", "type": "string" } }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "minimum": 1, - "title": "Page", - "type": "integer" - } - }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 20, - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, - { - "in": "query", - "name": "started_after", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Started After" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9108,7 +11100,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CompatibleRunListResponse" + "$ref": "#/components/schemas/AgentRunResponse" } } }, @@ -9125,105 +11117,34 @@ "description": "Validation Error" } }, - "summary": "List Compatible Runs", + "summary": "Cancel an agent run", "tags": [ - "agent-evaluations" + "agents" ] - } - }, - "/agents/evaluation-criteria/{criteria_id}/results": { + }, "get": { - "description": "List evaluation results for a criteria with optional filtering.\n\nSupports filtering by status (pending, passed, failed, skipped, error),\nflagged-only mode, and an optional time range. Results are paginated\nwith configurable page size.", - "operationId": "list_evaluation_results_api_agents_evaluation_criteria__criteria_id__results_get", + "description": "Fetch the latest snapshot for an agent run created by `POST /agents/{agent_id}/runs` or `POST /agents/{agent_id}/runs/stream`.\n\nThe response includes `status`, `error_count`, and `output` once the run completes. Use `include_step_outputs=true` to include per-step outputs, timing, durations, and credits.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access runs belonging to your account.", + "operationId": "get_agent_run_api_agents_runs__run_id__get", "parameters": [ { - "in": "path", - "name": "criteria_id", - "required": true, - "schema": { - "title": "Criteria Id", - "type": "string" - } - }, - { - "in": "query", - "name": "status", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Status" - } - }, - { - "in": "query", - "name": "flagged_only", - "required": false, - "schema": { - "default": false, - "title": "Flagged Only", - "type": "boolean" - } - }, - { - "in": "query", - "name": "time_from", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Time From" - } - }, - { - "in": "query", - "name": "time_to", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Time To" - } - }, - { - "in": "query", - "name": "page", - "required": false, + "in": "path", + "name": "run_id", + "required": true, "schema": { - "default": 1, - "minimum": 1, - "title": "Page", - "type": "integer" + "title": "Run Id", + "type": "string" } }, { + "description": "If true, include per-step outputs with timing, durations, and credits.", "in": "query", - "name": "limit", + "name": "include_step_outputs", "required": false, "schema": { - "default": 20, - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" + "default": false, + "description": "If true, include per-step outputs with timing, durations, and credits.", + "title": "Include Step Outputs", + "type": "boolean" } }, { @@ -9235,7 +11156,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationResultListResponse" + "$ref": "#/components/schemas/AgentRunResponse" } } }, @@ -9252,21 +11173,23 @@ "description": "Validation Error" } }, - "summary": "List Evaluation Results", + "summary": "Get an agent run", "tags": [ - "agent-evaluations" + "agents" ] - }, - "post": { - "description": "Record an evaluation result for a criteria.\n\nUse this endpoint to push results from external test harnesses, CI/CD\npipelines, or custom evaluation logic. Each result is linked to an\nagent run and optionally a specific step run.", - "operationId": "create_evaluation_result_api_agents_evaluation_criteria__criteria_id__results_post", + } + }, + "/agents/{agent_id}": { + "delete": { + "description": "Soft-delete an agent. The agent will no longer appear in listings or be accessible via the API.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only delete agents belonging to your account.", + "operationId": "delete_agent_api_agents__agent_id__delete", "parameters": [ { "in": "path", - "name": "criteria_id", + "name": "agent_id", "required": true, "schema": { - "title": "Criteria Id", + "title": "Agent Id", "type": "string" } }, @@ -9274,22 +11197,49 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateEvaluationResultRequest" + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } + }, + "description": "Validation Error" + } + }, + "summary": "Delete an agent", + "tags": [ + "agents" + ] + }, + "get": { + "description": "Fetch an agent's metadata (name, description, trigger type, timestamps).\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access agents belonging to your account.", + "operationId": "get_agent_metadata_api_agents__agent_id__get", + "parameters": [ + { + "in": "path", + "name": "agent_id", + "required": true, + "schema": { + "title": "Agent Id", + "type": "string" } }, - "required": true - }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], "responses": { - "201": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationResultResponse" + "$ref": "#/components/schemas/AgentSummaryResponse" } } }, @@ -9306,23 +11256,21 @@ "description": "Validation Error" } }, - "summary": "Create Evaluation Result", + "summary": "Get an agent", "tags": [ - "agent-evaluations" + "agents" ] - } - }, - "/agents/evaluation-criteria/{criteria_id}/summary": { - "get": { - "description": "Get an aggregated summary of evaluation results for a criteria.\n\nReturns total, passed, failed, error, and flagged counts alongside the\naverage score, giving a quick overview of how the criteria is performing.", - "operationId": "get_evaluation_summary_api_agents_evaluation_criteria__criteria_id__summary_get", + }, + "put": { + "description": "Update an agent's name, description, evaluation settings, and model lifecycle settings.\n\nEvaluation settings: `evaluation_mode` ('output_expectation', 'eval_and_retry', 'sample_and_flag'), `default_evaluation_tier` ('fast', 'balanced', 'thorough'), `max_retries`, `retry_on_failure`, `sampling_config`.\n\nModel lifecycle settings: `prompt_model_auto_upgrade_strategy` ('none', 'early_adopter', 'middle_of_road', 'cautious_adopter'), `prompt_model_auto_rollback_enabled`, `prompt_model_auto_rollback_triggers` (list of 'agent_eval_fail', 'governance_flag', 'governance_block', 'agent_run_failed').\n\nReplacing the workflow from an export:\n- Pass `agent_definition` with the JSON shape produced by `GET /agents/{id}/export`. Update only touches the workflow + agent metadata \u2014 alert_configs, evaluation_criteria, governance_policies, schedules, and solution links from the imported file are NOT applied (use the dedicated endpoints for those, or `POST /agents` to import as a new agent).\n- `entity_remap: {source_uuid: target_uuid}` substitutes workflow entity refs before save (same shape as `POST /agents`).\n\nAt least one field must be provided.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only update agents belonging to your account.", + "operationId": "update_agent_api_agents__agent_id__put", "parameters": [ { "in": "path", - "name": "criteria_id", + "name": "agent_id", "required": true, "schema": { - "title": "Criteria Id", + "title": "Agent Id", "type": "string" } }, @@ -9330,12 +11278,22 @@ "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__agents__UpdateAgentRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationResultSummaryResponse" + "$ref": "#/components/schemas/AgentSummaryResponse" } } }, @@ -9345,39 +11303,48 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" } } }, - "description": "Validation Error" + "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." } }, - "summary": "Get Evaluation Summary", + "summary": "Update agent metadata", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/evaluation-results/non-manual-summary": { + "/agents/{agent_id}/ai-assistant/conversations": { "get": { - "description": "Get account-level evaluation summary for API key clients.\n\nReturns aggregated pass/fail/flagged counts and pass rates for each evaluation mode (eval_and_retry, sample_and_flag).", - "operationId": "get_non_manual_evaluation_summary_api_agents_evaluation_results_non_manual_summary_get", + "description": "Fetch the AI assistant conversation history for a specific step of an agent.\n\nReturns past conversation turns (user inputs, AI responses, accept/decline status) ordered oldest first. Use `step_type` to filter by step type, and optionally `step_id` to narrow to a specific step instance.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be queried.", + "operationId": "get_ai_conversation_history_api_agents__agent_id__ai_assistant_conversations_get", "parameters": [ { + "in": "path", + "name": "agent_id", + "required": true, + "schema": { + "title": "Agent Id", + "type": "string" + } + }, + { + "description": "Step type to look up.", "in": "query", - "name": "days", - "required": false, + "name": "step_type", + "required": true, "schema": { - "default": 30, - "maximum": 730, - "minimum": 1, - "title": "Days", - "type": "integer" + "description": "Step type to look up.", + "title": "Step Type", + "type": "string" } }, { + "description": "Step ID to filter by.", "in": "query", - "name": "start_date", + "name": "step_id", "required": false, "schema": { "anyOf": [ @@ -9388,35 +11355,103 @@ "type": "null" } ], - "title": "Start Date" + "description": "Step ID to filter by.", + "title": "Step Id" } }, { + "description": "Max turns to return.", "in": "query", - "name": "end_date", + "name": "limit", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "default": 10, + "description": "Max turns to return.", + "maximum": 50, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "description": "Number of recent turns to skip.", + "in": "query", + "name": "offset", + "required": false, + "schema": { + "default": 0, + "description": "Number of recent turns to skip.", + "minimum": 0, + "title": "Offset", + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiConversationHistoryResponse" } - ], - "title": "End Date" + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get AI conversation history", + "tags": [ + "agents" + ] + } + }, + "/agents/{agent_id}/ai-assistant/generate-steps": { + "post": { + "description": "Use the AI assistant to generate a full agent step workflow from a natural language description.\n\nProvide a description of what the agent should do, along with optional context (current steps, trigger type). The AI produces a complete set of agent steps.\nUse mode 'generate_full' for new workflows or 'modify_workflow' to refine existing ones.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be used.", + "operationId": "generate_agent_steps_api_agents__agent_id__ai_assistant_generate_steps_post", + "parameters": [ + { + "in": "path", + "name": "agent_id", + "required": true, + "schema": { + "title": "Agent Id", + "type": "string" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateAgentStepsRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/schemas__v1__agent_evaluations__NonManualEvaluationSummaryResponse" + "$ref": "#/components/schemas/GenerateAgentStepsResponse" } } }, @@ -9433,17 +11468,26 @@ "description": "Validation Error" } }, - "summary": "Get Non Manual Evaluation Summary", + "summary": "Generate agent workflow", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/preview-import": { + "/agents/{agent_id}/ai-assistant/step-config": { "post": { - "description": "Validate an `agent_definition` payload (the same shape produced by `GET /api/agents/{agent_id}/export`) without creating or modifying any agent. On success returns a summary the client can show before commit (counts of steps, schedules, alert configs, evaluation criteria, governance policies). On failure returns the same 422 body shape used by `POST /api/agents` and `PUT /api/agents/{id}` so callers can render line/column-anchored errors.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. No DB writes.", - "operationId": "preview_import_agent_api_agents_preview_import_post", + "description": "Use the AI assistant to generate or refine a single step's configuration.\n\nProvide the step type, a natural language instruction, and optionally the current configuration. The AI will produce a proposed configuration along with an explanation. The suggestion is stored as a conversation turn that can be accepted or declined separately via the mark endpoint.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be used.", + "operationId": "generate_step_config_api_agents__agent_id__ai_assistant_step_config_post", "parameters": [ + { + "in": "path", + "name": "agent_id", + "required": true, + "schema": { + "title": "Agent Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9452,7 +11496,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__agents__AgentImportPreviewRequest" + "$ref": "#/components/schemas/GenerateStepConfigRequest" } } }, @@ -9463,7 +11507,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__agents__AgentImportPreviewResponse" + "$ref": "#/components/schemas/GenerateStepConfigResponse" } } }, @@ -9473,24 +11517,42 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" + "$ref": "#/components/schemas/HTTPValidationError" } } }, - "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." + "description": "Validation Error" } }, - "summary": "Preview an agent_definition import", + "summary": "Generate step configuration", "tags": [ "agents" ] } }, - "/agents/runs/search": { - "post": { - "description": "Search agent traces using semantic similarity.\n\nFinds step-run outputs that are most semantically similar to the query.\nResults include the matching text, agent/step metadata, and a similarity score.\n\nAgent traces are automatically indexed when runs complete. The first 7 days of storage are free; extended retention is billed.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Searches only within your account's traces.", - "operationId": "search_agent_runs_api_agents_runs_search_post", + "/agents/{agent_id}/ai-assistant/{conversation_id}": { + "patch": { + "description": "Accept or decline a proposed AI assistant configuration for a conversation turn.\n\nThis only updates the tracking status on the conversation record. To actually apply the proposed configuration, use the agent definition update endpoint separately.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. The conversation must belong to one of your agents.", + "operationId": "mark_ai_suggestion_api_agents__agent_id__ai_assistant__conversation_id__patch", "parameters": [ + { + "in": "path", + "name": "agent_id", + "required": true, + "schema": { + "title": "Agent Id", + "type": "string" + } + }, + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "title": "Conversation Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9499,7 +11561,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__agents__AgentTraceSearchRequest" + "$ref": "#/components/schemas/MarkAiSuggestionRequest" } } }, @@ -9510,7 +11572,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentTraceSearchResponse" + "additionalProperties": { + "type": "boolean" + }, + "title": "Response Mark Ai Suggestion Api Agents Agent Id Ai Assistant Conversation Id Patch", + "type": "object" } } }, @@ -9527,23 +11593,23 @@ "description": "Validation Error" } }, - "summary": "Search agent traces", + "summary": "Accept or decline suggestion", "tags": [ "agents" ] } }, - "/agents/runs/{run_id}": { - "delete": { - "description": "Cancel a running agent run.\n\nIf the run is already in a terminal state (`completed` or `failed`), cancellation will be rejected.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only cancel runs belonging to your account.", - "operationId": "delete_agent_run_api_agents_runs__run_id__delete", + "/agents/{agent_id}/attachment-references": { + "get": { + "description": "Return the static attachment-reference contract for an agent \u2014 what files the agent's definition expects on a run.\n\nCall this BEFORE staging uploads so you know whether the agent accepts files at all (``requires_uploads``), and which specific filenames/indexes/patterns the templates reference. Mismatched batches are rejected at run time with HTTP 400.", + "operationId": "api_get_agent_attachment_references_api_agents__agent_id__attachment_references_get", "parameters": [ { "in": "path", - "name": "run_id", + "name": "agent_id", "required": true, "schema": { - "title": "Run Id", + "title": "Agent Id", "type": "string" } }, @@ -9556,7 +11622,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentRunResponse" + "$ref": "#/components/schemas/AgentAttachmentRefsApiResponse" } } }, @@ -9573,36 +11639,26 @@ "description": "Validation Error" } }, - "summary": "Cancel an agent run", + "summary": "Get agent attachment-reference contract", "tags": [ "agents" ] - }, + } + }, + "/agents/{agent_id}/callers": { "get": { - "description": "Fetch the latest snapshot for an agent run created by `POST /agents/{agent_id}/runs` or `POST /agents/{agent_id}/runs/stream`.\n\nThe response includes `status`, `error_count`, and `output` once the run completes. Use `include_step_outputs=true` to include per-step outputs, timing, durations, and credits.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access runs belonging to your account.", - "operationId": "get_agent_run_api_agents_runs__run_id__get", + "description": "List the live agents that call this agent via a `call_agent` step. They must be disabled before this agent can be paused.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; the agent must belong to the key's account.", + "operationId": "get_agent_callers_api_api_agents__agent_id__callers_get", "parameters": [ { "in": "path", - "name": "run_id", + "name": "agent_id", "required": true, "schema": { - "title": "Run Id", + "title": "Agent Id", "type": "string" } }, - { - "description": "If true, include per-step outputs with timing, durations, and credits.", - "in": "query", - "name": "include_step_outputs", - "required": false, - "schema": { - "default": false, - "description": "If true, include per-step outputs with timing, durations, and credits.", - "title": "Include Step Outputs", - "type": "boolean" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9612,7 +11668,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentRunResponse" + "items": { + "$ref": "#/components/schemas/AgentCallerApiResponse" + }, + "title": "Response Get Agent Callers Api Api Agents Agent Id Callers Get", + "type": "array" } } }, @@ -9629,16 +11689,16 @@ "description": "Validation Error" } }, - "summary": "Get an agent run", + "summary": "List agents that call this agent", "tags": [ "agents" ] } }, - "/agents/{agent_id}": { - "delete": { - "description": "Soft-delete an agent. The agent will no longer appear in listings or be accessible via the API.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only delete agents belonging to your account.", - "operationId": "delete_agent_api_agents__agent_id__delete", + "/agents/{agent_id}/definition": { + "get": { + "description": "Fetch the current agent definition from the main branch.\n\nThe response includes `change_id` which must be provided when updating the definition (optimistic locking).\n\nThe definition contains the agent's step workflow. Available step types:\n- `prompt_call`: Call an LLM with a prompt template\n- `retrieval`: Search a knowledge base\n- `regex_replace`: Reshape text via ordered regex find/replace rules\n- `gate`: Evaluate conditions, stop or continue child execution\n- `retry`: Re-execute from a target ancestor step (for quality-control loops; pair with a `gate` step for conditional retrying. Fields: `target_step_id` (ancestor step ID), `max_retries` (1\u201310))\n- `evaluate_step`: Score a selected previous step output and emit JSON with `score`, `passed`, and `pass_threshold` (fields: `target_step_id`, `evaluation_prompt`, `pass_threshold`, optional `evaluation_tier`, optional `expectation_config`)\n- `extract_data`: Progressively read and analyze large input\n- `extract_content`: Extract structured data (JSON, HTML, XML)\n- `add_chat_turn` / `load_chat_history`: Record a turn or load running history from a conversation memory bank\n- `add_memory` / `search_memory` / `load_memory`: Write, semantic-search, or load entries on a general memory bank\n- `send_email`: Send email with step output\n- `webhook_call`: POST data to an external URL\n- `write_aws_s3_object`: Write output to S3\n- `call_agent`: Invoke another agent\n- `write_metadata`: Write a value to content metadata (for filtering/gates; content-triggered agents only. Fields: `metadata_key`, `content`)\n- `write_content_attachment`: Write a file-backed attachment to content (optionally indexed for retrieval; content-triggered agents only. Fields: `attachment_key`, `content`, `content_type`, `indexed`)\n- `load_content_attachment`: Load a previously written attachment (content-triggered agents only. Fields: `attachment_key`)\n- `load_content`: Load the full text body of a source document (typically used with content-triggered agents; can also load by explicit `content_version_id`. Fields: `content_version_id` optional)\n- `streaming_result`: Stream LLM tokens in real-time via SSE (must be a direct child of `prompt_call`; requires `dynamic_input` or `template_input` trigger; `priority: true` enables real-time streaming)\n- `display_result`: Show output to the user\n- `join`: Merge parallel branches\n- `merge`: Combine multiple inputs into a single templated output\n- `text`: Static text literal\n- `for_each`: Iterate a body over a list of items (body lives in `body[]`)\n- `if_else`: Conditional dispatch. Evaluates `conditions` (same shape as `gate`) and runs `then_steps` on match, otherwise the optional `else_steps`. The chosen branch's output flows to the if_else step's own `child_steps` (post-branch continuation chain). **`display_result` and `streaming_result` are not allowed inside `then_steps` / `else_steps`** \u2014 end each branch with a content-producing step (e.g. `text`, `prompt_call`) and place the single `display_result` in `child_steps`.\n- `switch`: Single-discriminator dispatch. Renders `discriminator` (default `{{input}}`) and routes to the first matching `cases[]` entry (equality by default; pass a list in `match` for `$in` semantics) or to `else_steps` when nothing matches. The chosen case's output flows to the switch step's own `child_steps`. **`display_result` and `streaming_result` are not allowed inside `cases[].steps` or `else_steps`** \u2014 end each case with a content-producing step and place the single `display_result` in `child_steps`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access agents belonging to your account.", + "operationId": "get_agent_definition_api_agents__agent_id__definition_get", "parameters": [ { "in": "path", @@ -9654,7 +11714,14 @@ } ], "responses": { - "204": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentDefinitionResponse" + } + } + }, "description": "Successful Response" }, "422": { @@ -9668,14 +11735,14 @@ "description": "Validation Error" } }, - "summary": "Delete an agent", + "summary": "Get agent definition", "tags": [ "agents" ] }, - "get": { - "description": "Fetch an agent's metadata (name, description, trigger type, timestamps).\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access agents belonging to your account.", - "operationId": "get_agent_metadata_api_agents__agent_id__get", + "put": { + "description": "Update the agent's definition on the main branch.\n\nUses **optimistic locking**: provide `expected_change_id` from the last `GET /api/agents/{agent_id}/definition`. Returns `409 Conflict` if the definition was modified since your last read.\n\nThe definition contains the agent's step workflow. Step types include `prompt_call`, `retrieval`, `regex_replace`, `gate`, `retry`, `evaluate_step`, `extract_data`, `extract_content`, `add_chat_turn`, `load_chat_history`, `add_memory`, `search_memory`, `load_memory`, `streaming_result`, `send_email`, `webhook_call`, `write_aws_s3_object`, `call_agent`, `write_metadata`, `write_content_attachment`, `load_content_attachment`, `load_content`, `display_result`, `join`, `merge`, `text`, `for_each`, `if_else`, and `switch`. Non-composite step types (`display_result`, `join`, `retry`, `streaming_result`) cannot contain child steps.\n\n**Retry steps** re-execute from a target ancestor step for quality-control loops. Configure with `target_step_id` (ancestor step ID) and `max_retries` (1\u201310). Best practice: place a `gate` step before the retry to make retries conditional.\n\n**if_else** runs `then_steps` when its `conditions` (same shape as `gate`) match, otherwise its optional `else_steps`. Either branch's output flows to the if_else step's own `child_steps` (the post-branch continuation chain).\n\n**switch** dispatches on a `discriminator` template (default `{{input}}`) to the first matching case (equality by default; pass a list in `match` for `$in` semantics) or to `else_steps` when no case matches. Cases own their own `steps` subtrees; the chosen branch's output flows to the switch step's own `child_steps`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only update agents belonging to your account.", + "operationId": "update_agent_definition_api_agents__agent_id__definition_put", "parameters": [ { "in": "path", @@ -9690,12 +11757,22 @@ "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAgentDefinitionRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentSummaryResponse" + "$ref": "#/components/schemas/AgentDefinitionResponse" } } }, @@ -9712,14 +11789,16 @@ "description": "Validation Error" } }, - "summary": "Get an agent", + "summary": "Update agent definition", "tags": [ "agents" ] - }, - "put": { - "description": "Update an agent's name, description, evaluation settings, and model lifecycle settings.\n\nEvaluation settings: `evaluation_mode` ('output_expectation', 'eval_and_retry', 'sample_and_flag'), `default_evaluation_tier` ('fast', 'balanced', 'thorough'), `max_retries`, `retry_on_failure`, `sampling_config`.\n\nModel lifecycle settings: `prompt_model_auto_upgrade_strategy` ('none', 'early_adopter', 'middle_of_road', 'cautious_adopter'), `prompt_model_auto_rollback_enabled`, `prompt_model_auto_rollback_triggers` (list of 'agent_eval_fail', 'governance_flag', 'governance_block', 'agent_run_failed').\n\nReplacing the workflow from an export:\n- Pass `agent_definition` with the JSON shape produced by `GET /agents/{id}/export`. Update only touches the workflow + agent metadata \u2014 alert_configs, evaluation_criteria, governance_policies, schedules, and solution links from the imported file are NOT applied (use the dedicated endpoints for those, or `POST /agents` to import as a new agent).\n- `entity_remap: {source_uuid: target_uuid}` substitutes workflow entity refs before save (same shape as `POST /agents`).\n\nAt least one field must be provided.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only update agents belonging to your account.", - "operationId": "update_agent_api_agents__agent_id__put", + } + }, + "/agents/{agent_id}/disable": { + "post": { + "description": "Disable an agent so it stops firing from every trigger path (API runs return 409, inbound email is turned away, scheduled/content triggers are skipped).\n\nReturns **409** with the blocking callers when other live agents still call this one via a `call_agent` step \u2014 disable those first.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must belong to the key's account.", + "operationId": "disable_agent_api_api_agents__agent_id__disable_post", "parameters": [ { "in": "path", @@ -9734,16 +11813,6 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/routers__api__agents__UpdateAgentRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { @@ -9759,23 +11828,23 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentDefinitionImportErrorResponse" + "$ref": "#/components/schemas/HTTPValidationError" } } }, - "description": "The supplied `agent_definition` payload failed validation. The body lists each error with a 1-indexed line/column pointing into the canonical pretty-printed echo of the payload (also returned in `source`)." + "description": "Validation Error" } }, - "summary": "Update agent metadata", + "summary": "Pause (disable) an agent", "tags": [ "agents" ] } }, - "/agents/{agent_id}/ai-assistant/conversations": { - "get": { - "description": "Fetch the AI assistant conversation history for a specific step of an agent.\n\nReturns past conversation turns (user inputs, AI responses, accept/decline status) ordered oldest first. Use `step_type` to filter by step type, and optionally `step_id` to narrow to a specific step instance.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be queried.", - "operationId": "get_ai_conversation_history_api_agents__agent_id__ai_assistant_conversations_get", + "/agents/{agent_id}/enable": { + "post": { + "description": "Re-enable a paused agent (clears the disable state, whether it was paused manually or auto-paused by the inbound-email overload safeguard).\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must belong to the key's account.", + "operationId": "enable_agent_api_api_agents__agent_id__enable_post", "parameters": [ { "in": "path", @@ -9786,62 +11855,6 @@ "type": "string" } }, - { - "description": "Step type to look up.", - "in": "query", - "name": "step_type", - "required": true, - "schema": { - "description": "Step type to look up.", - "title": "Step Type", - "type": "string" - } - }, - { - "description": "Step ID to filter by.", - "in": "query", - "name": "step_id", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Step ID to filter by.", - "title": "Step Id" - } - }, - { - "description": "Max turns to return.", - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 10, - "description": "Max turns to return.", - "maximum": 50, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, - { - "description": "Number of recent turns to skip.", - "in": "query", - "name": "offset", - "required": false, - "schema": { - "default": 0, - "description": "Number of recent turns to skip.", - "minimum": 0, - "title": "Offset", - "type": "integer" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -9851,7 +11864,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiConversationHistoryResponse" + "$ref": "#/components/schemas/AgentSummaryResponse" } } }, @@ -9868,16 +11881,16 @@ "description": "Validation Error" } }, - "summary": "Get AI conversation history", + "summary": "Resume (enable) a paused agent", "tags": [ "agents" ] } }, - "/agents/{agent_id}/ai-assistant/generate-steps": { - "post": { - "description": "Use the AI assistant to generate a full agent step workflow from a natural language description.\n\nProvide a description of what the agent should do, along with optional context (current steps, trigger type). The AI produces a complete set of agent steps.\nUse mode 'generate_full' for new workflows or 'modify_workflow' to refine existing ones.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be used.", - "operationId": "generate_agent_steps_api_agents__agent_id__ai_assistant_generate_steps_post", + "/agents/{agent_id}/evaluation-criteria": { + "get": { + "description": "List all evaluation criteria configured for an agent.\n\nReturns every criteria with its type, configuration, and a summary of\nresults (pass / fail counts). Criteria can be filtered client-side by\ntype or enabled status.", + "operationId": "list_evaluation_criteria_api_agents__agent_id__evaluation_criteria_get", "parameters": [ { "in": "path", @@ -9892,22 +11905,16 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerateAgentStepsRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerateAgentStepsResponse" + "items": { + "$ref": "#/components/schemas/EvaluationCriteriaResponse" + }, + "title": "Response List Evaluation Criteria Api Agents Agent Id Evaluation Criteria Get", + "type": "array" } } }, @@ -9924,16 +11931,14 @@ "description": "Validation Error" } }, - "summary": "Generate agent workflow", + "summary": "List Evaluation Criteria", "tags": [ - "agents" + "agent-evaluations" ] - } - }, - "/agents/{agent_id}/ai-assistant/step-config": { - "post": { - "description": "Use the AI assistant to generate or refine a single step's configuration.\n\nProvide the step type, a natural language instruction, and optionally the current configuration. The AI will produce a proposed configuration along with an explanation. The suggestion is stored as a conversation turn that can be accepted or declined separately via the mark endpoint.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Only agents belonging to your account can be used.", - "operationId": "generate_step_config_api_agents__agent_id__ai_assistant_step_config_post", + }, + "post": { + "description": "Create new step evaluation settings for an agent.\n\nThe evaluation mode, retry settings, and sample frequency are inherited\nfrom the agent and stored on the criteria row for historical reference.", + "operationId": "create_evaluation_criteria_api_agents__agent_id__evaluation_criteria_post", "parameters": [ { "in": "path", @@ -9952,18 +11957,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerateStepConfigRequest" + "$ref": "#/components/schemas/CreateEvaluationCriteriaRequest" } } }, "required": true }, "responses": { - "200": { + "201": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerateStepConfigResponse" + "$ref": "#/components/schemas/EvaluationCriteriaResponse" } } }, @@ -9980,16 +11985,16 @@ "description": "Validation Error" } }, - "summary": "Generate step configuration", + "summary": "Create Evaluation Criteria", "tags": [ - "agents" + "agent-evaluations" ] } }, - "/agents/{agent_id}/ai-assistant/{conversation_id}": { - "patch": { - "description": "Accept or decline a proposed AI assistant configuration for a conversation turn.\n\nThis only updates the tracking status on the conversation record. To actually apply the proposed configuration, use the agent definition update endpoint separately.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. The conversation must belong to one of your agents.", - "operationId": "mark_ai_suggestion_api_agents__agent_id__ai_assistant__conversation_id__patch", + "/agents/{agent_id}/evaluation-criteria/test-draft": { + "post": { + "description": "Run an ephemeral evaluation against provided step output without persisting results.\n\nUse this to interactively test evaluation prompts and expectation\nconfigurations while editing criteria. No credits are consumed because\nthe result is not recorded.", + "operationId": "test_draft_evaluation_api_agents__agent_id__evaluation_criteria_test_draft_post", "parameters": [ { "in": "path", @@ -10000,15 +12005,6 @@ "type": "string" } }, - { - "in": "path", - "name": "conversation_id", - "required": true, - "schema": { - "title": "Conversation Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -10017,7 +12013,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MarkAiSuggestionRequest" + "$ref": "#/components/schemas/TestDraftEvaluationRequest" } } }, @@ -10028,11 +12024,7 @@ "content": { "application/json": { "schema": { - "additionalProperties": { - "type": "boolean" - }, - "title": "Response Mark Ai Suggestion Api Agents Agent Id Ai Assistant Conversation Id Patch", - "type": "object" + "$ref": "#/components/schemas/TestDraftEvaluationResponse" } } }, @@ -10049,16 +12041,16 @@ "description": "Validation Error" } }, - "summary": "Accept or decline suggestion", + "summary": "Test Draft Evaluation", "tags": [ - "agents" + "agent-evaluations" ] } }, - "/agents/{agent_id}/attachment-references": { + "/agents/{agent_id}/evaluation-results": { "get": { - "description": "Return the static attachment-reference contract for an agent \u2014 what files the agent's definition expects on a run.\n\nCall this BEFORE staging uploads so you know whether the agent accepts files at all (``requires_uploads``), and which specific filenames/indexes/patterns the templates reference. Mismatched batches are rejected at run time with HTTP 400.", - "operationId": "api_get_agent_attachment_references_api_agents__agent_id__attachment_references_get", + "description": "List evaluation results across all criteria configured on an agent.\n\nReturns a paginated list of evaluation results with optional filtering by status, criteria, and date range. Results include score, pass/fail status, and details.", + "operationId": "list_agent_evaluation_results_api_agents__agent_id__evaluation_results_get", "parameters": [ { "in": "path", @@ -10069,6 +12061,103 @@ "type": "string" } }, + { + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + { + "in": "query", + "name": "step", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Step" + } + }, + { + "in": "query", + "name": "flagged_only", + "required": false, + "schema": { + "default": false, + "title": "Flagged Only", + "type": "boolean" + } + }, + { + "in": "query", + "name": "time_from", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time From" + } + }, + { + "in": "query", + "name": "time_to", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time To" + } + }, + { + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "minimum": 1, + "title": "Page", + "type": "integer" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 20, + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -10078,7 +12167,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentAttachmentRefsApiResponse" + "$ref": "#/components/schemas/EvaluationResultWithCriteriaListResponse" } } }, @@ -10095,16 +12184,16 @@ "description": "Validation Error" } }, - "summary": "Get agent attachment-reference contract", + "summary": "List Agent Evaluation Results", "tags": [ - "agents" + "agent-evaluations" ] } }, - "/agents/{agent_id}/definition": { + "/agents/{agent_id}/evaluation-runs": { "get": { - "description": "Fetch the current agent definition from the main branch.\n\nThe response includes `change_id` which must be provided when updating the definition (optimistic locking).\n\nThe definition contains the agent's step workflow. Available step types:\n- `prompt_call`: Call an LLM with a prompt template\n- `retrieval`: Search a knowledge base\n- `regex_replace`: Reshape text via ordered regex find/replace rules\n- `gate`: Evaluate conditions, stop or continue child execution\n- `retry`: Re-execute from a target ancestor step (for quality-control loops; pair with a `gate` step for conditional retrying. Fields: `target_step_id` (ancestor step ID), `max_retries` (1\u201310))\n- `evaluate_step`: Score a selected previous step output and emit JSON with `score`, `passed`, and `pass_threshold` (fields: `target_step_id`, `evaluation_prompt`, `pass_threshold`, optional `evaluation_tier`, optional `expectation_config`)\n- `extract_data`: Progressively read and analyze large input\n- `extract_content`: Extract structured data (JSON, HTML, XML)\n- `add_chat_turn` / `load_chat_history`: Record a turn or load running history from a conversation memory bank\n- `add_memory` / `search_memory` / `load_memory`: Write, semantic-search, or load entries on a general memory bank\n- `send_email`: Send email with step output\n- `webhook_call`: POST data to an external URL\n- `write_aws_s3_object`: Write output to S3\n- `call_agent`: Invoke another agent\n- `write_metadata`: Write a value to content metadata (for filtering/gates; content-triggered agents only. Fields: `metadata_key`, `content`)\n- `write_content_attachment`: Write a file-backed attachment to content (optionally indexed for retrieval; content-triggered agents only. Fields: `attachment_key`, `content`, `content_type`, `indexed`)\n- `load_content_attachment`: Load a previously written attachment (content-triggered agents only. Fields: `attachment_key`)\n- `load_content`: Load the full text body of a source document (typically used with content-triggered agents; can also load by explicit `content_version_id`. Fields: `content_version_id` optional)\n- `streaming_result`: Stream LLM tokens in real-time via SSE (must be a direct child of `prompt_call`; requires `dynamic_input` or `template_input` trigger; `priority: true` enables real-time streaming)\n- `display_result`: Show output to the user\n- `join`: Merge parallel branches\n- `merge`: Combine multiple inputs into a single templated output\n- `text`: Static text literal\n- `for_each`: Iterate a body over a list of items (body lives in `body[]`)\n- `if_else`: Conditional dispatch. Evaluates `conditions` (same shape as `gate`) and runs `then_steps` on match, otherwise the optional `else_steps`. The chosen branch's output flows to the if_else step's own `child_steps` (post-branch continuation chain). **`display_result` and `streaming_result` are not allowed inside `then_steps` / `else_steps`** \u2014 end each branch with a content-producing step (e.g. `text`, `prompt_call`) and place the single `display_result` in `child_steps`.\n- `switch`: Single-discriminator dispatch. Renders `discriminator` (default `{{input}}`) and routes to the first matching `cases[]` entry (equality by default; pass a list in `match` for `$in` semantics) or to `else_steps` when nothing matches. The chosen case's output flows to the switch step's own `child_steps`. **`display_result` and `streaming_result` are not allowed inside `cases[].steps` or `else_steps`** \u2014 end each case with a content-producing step and place the single `display_result` in `child_steps`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access agents belonging to your account.", - "operationId": "get_agent_definition_api_agents__agent_id__definition_get", + "description": "List evaluation results grouped by agent run.\n\nReturns paginated per-run summaries with pass/fail counts and optional\nfiltering by evaluation outcome, step, or date range.", + "operationId": "list_evaluation_runs_api_agents__agent_id__evaluation_runs_get", "parameters": [ { "in": "path", @@ -10116,69 +12205,102 @@ } }, { - "$ref": "#/components/parameters/X-Account-Id" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AgentDefinitionResponse" + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - } - }, - "description": "Successful Response" + ], + "title": "Status" + } }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + { + "in": "query", + "name": "step", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - } - }, - "description": "Validation Error" - } - }, - "summary": "Get agent definition", - "tags": [ - "agents" - ] - }, - "put": { - "description": "Update the agent's definition on the main branch.\n\nUses **optimistic locking**: provide `expected_change_id` from the last `GET /api/agents/{agent_id}/definition`. Returns `409 Conflict` if the definition was modified since your last read.\n\nThe definition contains the agent's step workflow. Step types include `prompt_call`, `retrieval`, `regex_replace`, `gate`, `retry`, `evaluate_step`, `extract_data`, `extract_content`, `add_chat_turn`, `load_chat_history`, `add_memory`, `search_memory`, `load_memory`, `streaming_result`, `send_email`, `webhook_call`, `write_aws_s3_object`, `call_agent`, `write_metadata`, `write_content_attachment`, `load_content_attachment`, `load_content`, `display_result`, `join`, `merge`, `text`, `for_each`, `if_else`, and `switch`. Non-composite step types (`display_result`, `join`, `retry`, `streaming_result`) cannot contain child steps.\n\n**Retry steps** re-execute from a target ancestor step for quality-control loops. Configure with `target_step_id` (ancestor step ID) and `max_retries` (1\u201310). Best practice: place a `gate` step before the retry to make retries conditional.\n\n**if_else** runs `then_steps` when its `conditions` (same shape as `gate`) match, otherwise its optional `else_steps`. Either branch's output flows to the if_else step's own `child_steps` (the post-branch continuation chain).\n\n**switch** dispatches on a `discriminator` template (default `{{input}}`) to the first matching case (equality by default; pass a list in `match` for `$in` semantics) or to `else_steps` when no case matches. Cases own their own `steps` subtrees; the chosen branch's output flows to the switch step's own `child_steps`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only update agents belonging to your account.", - "operationId": "update_agent_definition_api_agents__agent_id__definition_put", - "parameters": [ + ], + "title": "Step" + } + }, + { + "in": "query", + "name": "time_from", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time From" + } + }, + { + "in": "query", + "name": "time_to", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Time To" + } + }, + { + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "minimum": 1, + "title": "Page", + "type": "integer" + } + }, { - "in": "path", - "name": "agent_id", - "required": true, + "in": "query", + "name": "limit", + "required": false, "schema": { - "title": "Agent Id", - "type": "string" + "default": 20, + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateAgentDefinitionRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentDefinitionResponse" + "$ref": "#/components/schemas/EvaluationRunSummaryListResponse" } } }, @@ -10195,16 +12317,16 @@ "description": "Validation Error" } }, - "summary": "Update agent definition", + "summary": "List Evaluation Runs", "tags": [ - "agents" + "agent-evaluations" ] } }, - "/agents/{agent_id}/evaluation-criteria": { + "/agents/{agent_id}/export": { "get": { - "description": "List all evaluation criteria configured for an agent.\n\nReturns every criteria with its type, configuration, and a summary of\nresults (pass / fail counts). Criteria can be filtered client-side by\ntype or enabled status.", - "operationId": "list_evaluation_criteria_api_agents__agent_id__evaluation_criteria_get", + "description": "Export an agent definition as a portable JSON snapshot.\n\nThe response contains the full definition, trigger configuration with schedules, alert configs, evaluation criteria, agent-scoped governance policies, and a resolved dependency manifest that maps every referenced external entity UUID to its human-readable name.\n\nResponse shape:\n- `export_version`: schema version (currently `\"2\"`)\n- `exported_at`: ISO-8601 timestamp\n- `agent`: name, description, schema_version, definition, timestamps\n- `trigger`: trigger type, input template, schedules\n- `alert_configs`: alert type, thresholds, recipients\n- `evaluation_criteria`: evaluation settings per step\n- `governance_policies`: agent-scoped governance policies\n- `dependencies`: knowledge_bases, memory_banks, source_connections, agents, users\n\nQuery params:\n- `download` (default true): when true, sets `Content-Disposition: attachment` so clients treat the response as a file download.\n\nAuth & scoping:\n- Requires `X-API-Key`. You can only export agents belonging to your account.", + "operationId": "export_agent_api_agents__agent_id__export_get", "parameters": [ { "in": "path", @@ -10215,6 +12337,18 @@ "type": "string" } }, + { + "description": "Return as file download", + "in": "query", + "name": "download", + "required": false, + "schema": { + "default": true, + "description": "Return as file download", + "title": "Download", + "type": "boolean" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -10224,11 +12358,7 @@ "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/EvaluationCriteriaResponse" - }, - "title": "Response List Evaluation Criteria Api Agents Agent Id Evaluation Criteria Get", - "type": "array" + "$ref": "#/components/schemas/AgentExportResponse" } } }, @@ -10245,14 +12375,16 @@ "description": "Validation Error" } }, - "summary": "List Evaluation Criteria", + "summary": "Export agent definition", "tags": [ - "agent-evaluations" + "agents" ] - }, - "post": { - "description": "Create new step evaluation settings for an agent.\n\nThe evaluation mode, retry settings, and sample frequency are inherited\nfrom the agent and stored on the criteria row for historical reference.", - "operationId": "create_evaluation_criteria_api_agents__agent_id__evaluation_criteria_post", + } + }, + "/agents/{agent_id}/input-uploads/{upload_id}": { + "get": { + "description": "Poll the processing status of a file upload created via `POST /agents/{agent_id}/upload-input`.\n\nPossible `status` values: `processing`, `ready`, `failed`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", + "operationId": "api_get_agent_input_upload_status_api_agents__agent_id__input_uploads__upload_id__get", "parameters": [ { "in": "path", @@ -10263,59 +12395,12 @@ "type": "string" } }, - { - "$ref": "#/components/parameters/X-Account-Id" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateEvaluationCriteriaRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EvaluationCriteriaResponse" - } - } - }, - "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" - } - }, - "summary": "Create Evaluation Criteria", - "tags": [ - "agent-evaluations" - ] - } - }, - "/agents/{agent_id}/evaluation-criteria/test-draft": { - "post": { - "description": "Run an ephemeral evaluation against provided step output without persisting results.\n\nUse this to interactively test evaluation prompts and expectation\nconfigurations while editing criteria. No credits are consumed because\nthe result is not recorded.", - "operationId": "test_draft_evaluation_api_agents__agent_id__evaluation_criteria_test_draft_post", - "parameters": [ { "in": "path", - "name": "agent_id", + "name": "upload_id", "required": true, "schema": { - "title": "Agent Id", + "title": "Upload Id", "type": "string" } }, @@ -10323,22 +12408,12 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestDraftEvaluationRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestDraftEvaluationResponse" + "$ref": "#/components/schemas/UploadAgentInputApiResponse" } } }, @@ -10355,16 +12430,16 @@ "description": "Validation Error" } }, - "summary": "Test Draft Evaluation", + "summary": "Get upload status", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/{agent_id}/evaluation-results": { + "/agents/{agent_id}/runs": { "get": { - "description": "List evaluation results across all criteria configured on an agent.\n\nReturns a paginated list of evaluation results with optional filtering by status, criteria, and date range. Results include score, pass/fail status, and details.", - "operationId": "list_agent_evaluation_results_api_agents__agent_id__evaluation_results_get", + "description": "List runs for a specific agent (most recent first), with pagination.\n\nTypical use cases:\n- Build a traces UI for an agent.\n- Debug recent executions and inspect terminal statuses.\n\nNotes:\n- This endpoint returns a summary list. Fetch full details with `GET /agents/runs/{run_id}`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only list runs for agents in your account.", + "operationId": "list_agent_runs_api_agents__agent_id__runs_get", "parameters": [ { "in": "path", @@ -10376,117 +12451,129 @@ } }, { + "description": "Page number", "in": "query", - "name": "status", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Status" - } - }, - { - "in": "query", - "name": "step", + "name": "page", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Step" + "default": 1, + "description": "Page number", + "minimum": 1, + "title": "Page", + "type": "integer" } }, { + "description": "Items per page", "in": "query", - "name": "flagged_only", + "name": "limit", "required": false, "schema": { - "default": false, - "title": "Flagged Only", - "type": "boolean" + "default": 50, + "description": "Items per page", + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" } }, { + "description": "Filter runs by status", "in": "query", - "name": "time_from", + "name": "status", "required": false, "schema": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/PendingProcessingCompletedFailedStatus" }, { "type": "null" } ], - "title": "Time From" + "description": "Filter runs by status", + "title": "Status" } }, { - "in": "query", - "name": "time_to", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__agents__AgentRunListResponse" } - ], - "title": "Time To" - } - }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "minimum": 1, - "title": "Page", - "type": "integer" - } + } + }, + "description": "Successful Response" }, - { - "in": "query", - "name": "limit", - "required": false, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List agent runs", + "tags": [ + "agents" + ] + }, + "post": { + "description": "Start an agent run.\n\nAn *agent* is an automated workflow that can monitor content from your sources, process it with AI, and trigger actions. This endpoint creates a new run and returns a `run_id` you can poll to retrieve status and output.\n\nWhen to use:\n- Use this endpoint for request/response style integrations where polling is acceptable.\n- Use `POST /agents/{agent_id}/runs/stream` if you need real-time progress via SSE.\n\nKey fields:\n- `input`: text input for agents with a `dynamic_input` trigger.\n- `input_upload_id`: alternatively, reference a file previously uploaded via `POST /agents/{agent_id}/upload-input` (mutually exclusive with `input`).\n- `priority`: set true for latency-sensitive, user-facing work. For agents with a `streaming_result` step, set `priority=true` to enable real-time token streaming; otherwise the run still proceeds, but without live token streaming.\n- `metadata`: a JSON object that becomes available to agent steps for string substitution.\n\nAfter starting:\n- Poll `GET /agents/runs/{run_id}` until `status` is `completed` or `failed`.\n- Use `include_step_outputs=true` to include per-step outputs, timing, and credits.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", + "operationId": "run_agent_api_agents__agent_id__runs_post", + "parameters": [ + { + "in": "path", + "name": "agent_id", + "required": true, "schema": { - "default": 20, - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" + "title": "Agent Id", + "type": "string" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRunRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationResultWithCriteriaListResponse" + "$ref": "#/components/schemas/AgentRunResponse" } } }, "description": "Successful Response" }, + "402": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsufficientCreditsResponse" + } + } + }, + "description": "Insufficient credits \u2014 the account has exhausted its credits." + }, "422": { "content": { "application/json": { @@ -10498,16 +12585,16 @@ "description": "Validation Error" } }, - "summary": "List Agent Evaluation Results", + "summary": "Run an agent", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/{agent_id}/evaluation-runs": { - "get": { - "description": "List evaluation results grouped by agent run.\n\nReturns paginated per-run summaries with pass/fail counts and optional\nfiltering by evaluation outcome, step, or date range.", - "operationId": "list_evaluation_runs_api_agents__agent_id__evaluation_runs_get", + "/agents/{agent_id}/runs/stream": { + "post": { + "description": "Start a **priority** agent run and stream run events using Server-Sent Events (SSE).\n\nThis is the best option for interactive UIs where you want progress updates as the run executes.\n\nHow it works:\n- The first `init` event contains an `AgentRunResponse` snapshot, including the `run_id`.\n- Subsequent events are forwarded from the run event stream (status changes, step events, etc).\n- If the agent contains a `streaming_result` step, `stream_token` events deliver individual LLM tokens (with a `token` field) and a `stream_end` event signals completion.\n- The final `done` event contains the terminal snapshot (including `output` and `credits` when available).\n\nInput options (for `dynamic_input` triggers):\n- `input`: text input passed directly.\n- `input_upload_id`: reference a file uploaded via `POST /agents/{agent_id}/upload-input` (mutually exclusive with `input`).\n\nClient guidance:\n- Keep the connection open and handle keepalive comments.\n- On `timeout` or `error`, the payload includes `run_id` so clients can resume by polling `GET /agents/runs/{run_id}`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", + "operationId": "run_streaming_agent_api_agents__agent_id__runs_stream_post", "parameters": [ { "in": "path", @@ -10518,107 +12605,44 @@ "type": "string" } }, - { - "in": "query", - "name": "status", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Status" - } - }, - { - "in": "query", - "name": "step", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Step" - } - }, - { - "in": "query", - "name": "time_from", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Time From" - } - }, - { - "in": "query", - "name": "time_to", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Time To" - } - }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "minimum": 1, - "title": "Page", - "type": "integer" - } - }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 20, - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentRunStreamRequest" + } + } + }, + "required": true + }, "responses": { "200": { + "content": { + "application/json": { + "schema": {} + }, + "text/event-stream": { + "example": ": keepalive\n\nevent: init\ndata: {\"run_id\":\"...\",\"status\":\"pending\",\"error_count\":0,\"credits\":0.0,\"priority\":true,\"input\":\"...\",\"output\":null,\"attempts\":[]}\n\nevent: done\ndata: {\"run_id\":\"...\",\"status\":\"completed\",\"error_count\":0,\"credits\":0.0,\"priority\":true,\"input\":\"...\",\"output\":\"...\",\"attempts\":[]}\n\n", + "schema": { + "type": "string" + } + } + }, + "description": "Streams agent run events via Server-Sent Events (SSE); run is always created as priority.\n\nSSE events:\n- `event: init` \u2014 `data` is an `AgentRunResponse` snapshot (includes `run_id`).\n- `event: done` \u2014 `data` is the final `AgentRunResponse` snapshot (includes `output`, `credits`, etc).\n- Other events (e.g. `status`, step events) are forwarded from the run event stream.\n- On `timeout` / `error`, the payload includes `run_id` so clients can fetch status via `GET /api/agents/runs/{run_id}`." + }, + "402": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EvaluationRunSummaryListResponse" + "$ref": "#/components/schemas/InsufficientCreditsResponse" } } }, - "description": "Successful Response" + "description": "Insufficient credits \u2014 the account has exhausted its credits." }, "422": { "content": { @@ -10631,16 +12655,16 @@ "description": "Validation Error" } }, - "summary": "List Evaluation Runs", + "summary": "Run an agent (stream events)", "tags": [ - "agent-evaluations" + "agents" ] } }, - "/agents/{agent_id}/export": { + "/agents/{agent_id}/runs/{run_id}/evaluation-results": { "get": { - "description": "Export an agent definition as a portable JSON snapshot.\n\nThe response contains the full definition, trigger configuration with schedules, alert configs, evaluation criteria, agent-scoped governance policies, and a resolved dependency manifest that maps every referenced external entity UUID to its human-readable name.\n\nResponse shape:\n- `export_version`: schema version (currently `\"2\"`)\n- `exported_at`: ISO-8601 timestamp\n- `agent`: name, description, schema_version, definition, timestamps\n- `trigger`: trigger type, input template, schedules\n- `alert_configs`: alert type, thresholds, recipients\n- `evaluation_criteria`: evaluation settings per step\n- `governance_policies`: agent-scoped governance policies\n- `dependencies`: knowledge_bases, memory_banks, source_connections, agents, users\n\nQuery params:\n- `download` (default true): when true, sets `Content-Disposition: attachment` so clients treat the response as a file download.\n\nAuth & scoping:\n- Requires `X-API-Key`. You can only export agents belonging to your account.", - "operationId": "export_agent_api_agents__agent_id__export_get", + "description": "List all evaluation results recorded for a specific agent run.\n\nReturns results across all evaluation criteria for the given run,\nuseful for getting a complete quality snapshot of a single execution.", + "operationId": "list_run_evaluation_results_api_agents__agent_id__runs__run_id__evaluation_results_get", "parameters": [ { "in": "path", @@ -10652,15 +12676,12 @@ } }, { - "description": "Return as file download", - "in": "query", - "name": "download", - "required": false, + "in": "path", + "name": "run_id", + "required": true, "schema": { - "default": true, - "description": "Return as file download", - "title": "Download", - "type": "boolean" + "title": "Run Id", + "type": "string" } }, { @@ -10672,7 +12693,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentExportResponse" + "items": { + "$ref": "#/components/schemas/EvaluationResultWithCriteriaResponse" + }, + "title": "Response List Run Evaluation Results Api Agents Agent Id Runs Run Id Evaluation Results Get", + "type": "array" } } }, @@ -10689,16 +12714,16 @@ "description": "Validation Error" } }, - "summary": "Export agent definition", + "summary": "List Run Evaluation Results", "tags": [ - "agents" + "agent-evaluations" ] } }, - "/agents/{agent_id}/input-uploads/{upload_id}": { - "get": { - "description": "Poll the processing status of a file upload created via `POST /agents/{agent_id}/upload-input`.\n\nPossible `status` values: `processing`, `ready`, `failed`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", - "operationId": "api_get_agent_input_upload_status_api_agents__agent_id__input_uploads__upload_id__get", + "/agents/{agent_id}/triggers/{trigger_id}/email-config": { + "put": { + "description": "Set the custom alias, sender allowlist, and inbound-handling flags (`ignore_auto_generated`, `require_sender_auth`, `queue_on_quota`) on an agent's EMAIL_RECEIVED trigger, and return its computed email address(es). Omitted fields are left unchanged.\n\nAuth & scoping: requires `X-API-Key` header or OAuth Bearer token; the trigger must belong to an agent in the key's account.", + "operationId": "set_email_trigger_config_api_api_agents__agent_id__triggers__trigger_id__email_config_put", "parameters": [ { "in": "path", @@ -10711,10 +12736,10 @@ }, { "in": "path", - "name": "upload_id", + "name": "trigger_id", "required": true, "schema": { - "title": "Upload Id", + "title": "Trigger Id", "type": "string" } }, @@ -10722,12 +12747,22 @@ "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__agents__SetEmailTriggerConfigRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UploadAgentInputApiResponse" + "$ref": "#/components/schemas/EmailTriggerConfigResponse" } } }, @@ -10744,69 +12779,24 @@ "description": "Validation Error" } }, - "summary": "Get upload status", + "summary": "Configure an EMAIL_RECEIVED trigger", "tags": [ "agents" ] } }, - "/agents/{agent_id}/runs": { - "get": { - "description": "List runs for a specific agent (most recent first), with pagination.\n\nTypical use cases:\n- Build a traces UI for an agent.\n- Debug recent executions and inspect terminal statuses.\n\nNotes:\n- This endpoint returns a summary list. Fetch full details with `GET /agents/runs/{run_id}`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only list runs for agents in your account.", - "operationId": "list_agent_runs_api_agents__agent_id__runs_get", - "parameters": [ - { - "in": "path", - "name": "agent_id", - "required": true, - "schema": { - "title": "Agent Id", - "type": "string" - } - }, - { - "description": "Page number", - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "description": "Page number", - "minimum": 1, - "title": "Page", - "type": "integer" - } - }, - { - "description": "Items per page", - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 50, - "description": "Items per page", - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, + "/agents/{agent_id}/upload-input": { + "post": { + "description": "Upload a file to use as input for a `dynamic_input` agent run.\n\nSupports the same file types as content source uploads: text, PDF, DOCX, audio, video, images, etc. Text and document files are processed synchronously; audio/video are submitted for asynchronous transcription.\n\n**Size limit:** 200 MB per file.\n\n**Supported extensions:** txt, html, md, csv, xml, json, pdf, msg, docx, doc, pptx, ppt, xlsx, xls, zip, epub, png, jpg, gif, bmp, tiff, webp, mp3, wav, m4a, flac, ogg, mp4, mov, avi.\n\nAfter uploading, poll `GET /agents/{agent_id}/input-uploads/{upload_id}` until `status` is `ready`, then pass `input_upload_id` to `POST /agents/{agent_id}/runs`.\n\n**Multi-modal routing:** non-text uploads (image, audio, video, PDF) are surfaced natively to multi-modal-capable prompt steps; text-only models fall back to the OCR / transcript text counterpart. Audio originals are preserved past transcription so audio-capable models (Gemini 2.5/3, GPT-5 audio) read them directly; the agent-input-binary janitor sweeps originals once they pass your account's agent-trace retention (the agent-traces source's retention period; free default 7 days).\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", + "operationId": "api_upload_agent_input_api_agents__agent_id__upload_input_post", + "parameters": [ { - "description": "Filter runs by status", - "in": "query", - "name": "status", - "required": false, + "in": "path", + "name": "agent_id", + "required": true, "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/PendingProcessingCompletedFailedStatus" - }, - { - "type": "null" - } - ], - "description": "Filter runs by status", - "title": "Status" + "title": "Agent Id", + "type": "string" } }, { @@ -10814,11 +12804,11 @@ } ], "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__agents__AgentRunListResponse" + "$ref": "#/components/schemas/UploadAgentInputApiResponse" } } }, @@ -10835,24 +12825,17 @@ "description": "Validation Error" } }, - "summary": "List agent runs", + "summary": "Upload file input", "tags": [ "agents" ] - }, + } + }, + "/ai-assistant/feedback": { "post": { - "description": "Start an agent run.\n\nAn *agent* is an automated workflow that can monitor content from your sources, process it with AI, and trigger actions. This endpoint creates a new run and returns a `run_id` you can poll to retrieve status and output.\n\nWhen to use:\n- Use this endpoint for request/response style integrations where polling is acceptable.\n- Use `POST /agents/{agent_id}/runs/stream` if you need real-time progress via SSE.\n\nKey fields:\n- `input`: text input for agents with a `dynamic_input` trigger.\n- `input_upload_id`: alternatively, reference a file previously uploaded via `POST /agents/{agent_id}/upload-input` (mutually exclusive with `input`).\n- `priority`: set true for latency-sensitive, user-facing work. For agents with a `streaming_result` step, set `priority=true` to enable real-time token streaming; otherwise the run still proceeds, but without live token streaming.\n- `metadata`: a JSON object that becomes available to agent steps for string substitution.\n\nAfter starting:\n- Poll `GET /agents/runs/{run_id}` until `status` is `completed` or `failed`.\n- Use `include_step_outputs=true` to include per-step outputs, timing, and credits.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", - "operationId": "run_agent_api_agents__agent_id__runs_post", + "description": "Submit thumbs-up/down feedback on any AI assistant interaction. Negative feedback with a comment is analyzed for concerning issues.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_feedback_api_ai_assistant_feedback_post", "parameters": [ - { - "in": "path", - "name": "agent_id", - "required": true, - "schema": { - "title": "Agent Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -10861,7 +12844,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentRunRequest" + "$ref": "#/components/schemas/routers__api__ai_assistant__AiAssistantFeedbackRequest" } } }, @@ -10872,22 +12855,12 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentRunResponse" + "$ref": "#/components/schemas/AiAssistantFeedbackResponse" } } }, "description": "Successful Response" }, - "402": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InsufficientCreditsResponse" - } - } - }, - "description": "Insufficient credits \u2014 the account has exhausted its credits." - }, "422": { "content": { "application/json": { @@ -10899,26 +12872,17 @@ "description": "Validation Error" } }, - "summary": "Run an agent", + "summary": "Submit AI assistant feedback", "tags": [ - "agents" + "ai-assistant" ] } }, - "/agents/{agent_id}/runs/stream": { + "/ai-assistant/knowledge-base": { "post": { - "description": "Start a **priority** agent run and stream run events using Server-Sent Events (SSE).\n\nThis is the best option for interactive UIs where you want progress updates as the run executes.\n\nHow it works:\n- The first `init` event contains an `AgentRunResponse` snapshot, including the `run_id`.\n- Subsequent events are forwarded from the run event stream (status changes, step events, etc).\n- If the agent contains a `streaming_result` step, `stream_token` events deliver individual LLM tokens (with a `token` field) and a `stream_end` event signals completion.\n- The final `done` event contains the terminal snapshot (including `output` and `credits` when available).\n\nInput options (for `dynamic_input` triggers):\n- `input`: text input passed directly.\n- `input_upload_id`: reference a file uploaded via `POST /agents/{agent_id}/upload-input` (mutually exclusive with `input`).\n\nClient guidance:\n- Keep the connection open and handle keepalive comments.\n- On `timeout` or `error`, the payload includes `run_id` so clients can resume by polling `GET /agents/runs/{run_id}`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", - "operationId": "run_streaming_agent_api_agents__agent_id__runs_stream_post", + "description": "Generate a knowledge base creation/modification plan without requiring an existing solution. May also propose prerequisite source creation actions.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_knowledge_base_api_ai_assistant_knowledge_base_post", "parameters": [ - { - "in": "path", - "name": "agent_id", - "required": true, - "schema": { - "title": "Agent Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -10927,7 +12891,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentRunStreamRequest" + "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" } } }, @@ -10937,26 +12901,59 @@ "200": { "content": { "application/json": { - "schema": {} - }, - "text/event-stream": { - "example": ": keepalive\n\nevent: init\ndata: {\"run_id\":\"...\",\"status\":\"pending\",\"error_count\":0,\"credits\":0.0,\"priority\":true,\"input\":\"...\",\"output\":null,\"attempts\":[]}\n\nevent: done\ndata: {\"run_id\":\"...\",\"status\":\"completed\",\"error_count\":0,\"credits\":0.0,\"priority\":true,\"input\":\"...\",\"output\":\"...\",\"attempts\":[]}\n\n", "schema": { - "type": "string" + "$ref": "#/components/schemas/AiAssistantGenerateResponse" } } }, - "description": "Streams agent run events via Server-Sent Events (SSE); run is always created as priority.\n\nSSE events:\n- `event: init` \u2014 `data` is an `AgentRunResponse` snapshot (includes `run_id`).\n- `event: done` \u2014 `data` is the final `AgentRunResponse` snapshot (includes `output`, `credits`, etc).\n- Other events (e.g. `status`, step events) are forwarded from the run event stream.\n- On `timeout` / `error`, the payload includes `run_id` so clients can fetch status via `GET /api/agents/runs/{run_id}`." + "description": "Successful Response" }, - "402": { + "422": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InsufficientCreditsResponse" + "$ref": "#/components/schemas/HTTPValidationError" } } }, - "description": "Insufficient credits \u2014 the account has exhausted its credits." + "description": "Validation Error" + } + }, + "summary": "Generate a knowledge base plan (standalone)", + "tags": [ + "ai-assistant" + ] + } + }, + "/ai-assistant/memory-bank": { + "post": { + "description": "Generate a memory bank configuration suggestion via the AI assistant. The AI proposes name, type, mode, compaction prompt, and retention settings.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_memory_bank_api_ai_assistant_memory_bank_post", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankAiAssistantRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemoryBankAiAssistantResponse" + } + } + }, + "description": "Successful Response" }, "422": { "content": { @@ -10969,33 +12966,42 @@ "description": "Validation Error" } }, - "summary": "Run an agent (stream events)", + "summary": "Generate a memory bank configuration (standalone)", "tags": [ - "agents" + "ai-assistant" ] } }, - "/agents/{agent_id}/runs/{run_id}/evaluation-results": { + "/ai-assistant/memory-bank/last-conversation": { "get": { - "description": "List all evaluation results recorded for a specific agent run.\n\nReturns results across all evaluation criteria for the given run,\nuseful for getting a complete quality snapshot of a single execution.", - "operationId": "list_run_evaluation_results_api_agents__agent_id__runs__run_id__evaluation_results_get", + "description": "Fetch the most recent memory bank AI assistant conversation turns for the authenticated user. Returns turns in oldest-first order with a total count for pagination via limit/offset query parameters.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_memory_bank_history_api_ai_assistant_memory_bank_last_conversation_get", "parameters": [ { - "in": "path", - "name": "agent_id", - "required": true, + "description": "Max turns.", + "in": "query", + "name": "limit", + "required": false, "schema": { - "title": "Agent Id", - "type": "string" + "default": 5, + "description": "Max turns.", + "maximum": 50, + "minimum": 1, + "title": "Limit", + "type": "integer" } }, { - "in": "path", - "name": "run_id", - "required": true, + "description": "Skip count.", + "in": "query", + "name": "offset", + "required": false, "schema": { - "title": "Run Id", - "type": "string" + "default": 0, + "description": "Skip count.", + "minimum": 0, + "title": "Offset", + "type": "integer" } }, { @@ -11007,11 +13013,7 @@ "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/EvaluationResultWithCriteriaResponse" - }, - "title": "Response List Run Evaluation Results Api Agents Agent Id Runs Run Id Evaluation Results Get", - "type": "array" + "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankLastConversationResponse" } } }, @@ -11028,23 +13030,24 @@ "description": "Validation Error" } }, - "summary": "List Run Evaluation Results", + "summary": "Fetch memory bank AI conversation history", "tags": [ - "agent-evaluations" + "ai-assistant" ] } }, - "/agents/{agent_id}/upload-input": { - "post": { - "description": "Upload a file to use as input for a `dynamic_input` agent run.\n\nSupports the same file types as content source uploads: text, PDF, DOCX, audio, video, images, etc. Text and document files are processed synchronously; audio/video are submitted for asynchronous transcription.\n\n**Size limit:** 200 MB per file.\n\n**Supported extensions:** txt, html, md, csv, xml, json, pdf, msg, docx, doc, pptx, ppt, xlsx, xls, zip, epub, png, jpg, gif, bmp, tiff, webp, mp3, wav, m4a, flac, ogg, mp4, mov, avi.\n\nAfter uploading, poll `GET /agents/{agent_id}/input-uploads/{upload_id}` until `status` is `ready`, then pass `input_upload_id` to `POST /agents/{agent_id}/runs`.\n\n**Multi-modal routing:** non-text uploads (image, audio, video, PDF) are surfaced natively to multi-modal-capable prompt steps; text-only models fall back to the OCR / transcript text counterpart. Audio originals are preserved past transcription so audio-capable models (Gemini 2.5/3, GPT-5 audio) read them directly; the agent-input-binary janitor sweeps originals once they pass your account's agent-trace retention (the agent-traces source's retention period; free default 7 days).\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. All resources are scoped to the caller's account.", - "operationId": "api_upload_agent_input_api_agents__agent_id__upload_input_post", + "/ai-assistant/memory-bank/{conversation_id}": { + "patch": { + "description": "Update the acceptance status of a memory bank AI assistant conversation turn. Set ``accepted`` to true to accept the proposed configuration, or false to decline it. The accepted status is recorded for audit purposes.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_memory_bank_accept_api_ai_assistant_memory_bank__conversation_id__patch", "parameters": [ { "in": "path", - "name": "agent_id", + "name": "conversation_id", "required": true, "schema": { - "title": "Agent Id", + "format": "uuid", + "title": "Conversation Id", "type": "string" } }, @@ -11052,12 +13055,73 @@ "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankAcceptRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "additionalProperties": { + "type": "boolean" + }, + "title": "Response Api Ai Memory Bank Accept Api Ai Assistant Memory Bank Conversation Id Patch", + "type": "object" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Accept or decline a memory bank AI suggestion", + "tags": [ + "ai-assistant" + ] + } + }, + "/ai-assistant/solution": { + "post": { + "description": "Generate a complete solution plan covering sources, knowledge bases, and agents without requiring an existing solution. Supports SSE streaming when ``Accept: text/event-stream`` is set.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_solution_api_ai_assistant_solution_post", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" + } + } + }, + "required": true + }, "responses": { - "202": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UploadAgentInputApiResponse" + "$ref": "#/components/schemas/AiAssistantGenerateResponse" } } }, @@ -11074,16 +13138,16 @@ "description": "Validation Error" } }, - "summary": "Upload file input", + "summary": "Generate a solution plan (standalone)", "tags": [ - "agents" + "ai-assistant" ] } }, - "/ai-assistant/feedback": { + "/ai-assistant/source": { "post": { - "description": "Submit thumbs-up/down feedback on any AI assistant interaction. Negative feedback with a comment is analyzed for concerning issues.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_feedback_api_ai_assistant_feedback_post", + "description": "Generate a content source creation/modification plan without requiring an existing solution. The AI proposes actions for the user to review before any changes are made.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_source_api_ai_assistant_source_post", "parameters": [ { "$ref": "#/components/parameters/X-Account-Id" @@ -11093,7 +13157,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__ai_assistant__AiAssistantFeedbackRequest" + "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" } } }, @@ -11104,7 +13168,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiAssistantFeedbackResponse" + "$ref": "#/components/schemas/AiAssistantGenerateResponse" } } }, @@ -11121,17 +13185,27 @@ "description": "Validation Error" } }, - "summary": "Submit AI assistant feedback", + "summary": "Generate a source plan (standalone)", "tags": [ "ai-assistant" ] } }, - "/ai-assistant/knowledge-base": { + "/ai-assistant/{conversation_id}/accept": { "post": { - "description": "Generate a knowledge base creation/modification plan without requiring an existing solution. May also propose prerequisite source creation actions.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_knowledge_base_api_ai_assistant_knowledge_base_post", + "description": "Accept and execute a previously proposed standalone plan. If the plan contains destructive actions (deletions), ``confirm_deletions`` must be set to true.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_accept_api_ai_assistant__conversation_id__accept_post", "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "format": "uuid", + "title": "Conversation Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -11140,7 +13214,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" + "$ref": "#/components/schemas/routers__api__solutions__AiAssistantAcceptRequest" } } }, @@ -11151,7 +13225,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiAssistantGenerateResponse" + "$ref": "#/components/schemas/AiAssistantAcceptResponse" } } }, @@ -11168,37 +13242,188 @@ "description": "Validation Error" } }, - "summary": "Generate a knowledge base plan (standalone)", + "summary": "Accept and execute a standalone plan", "tags": [ "ai-assistant" ] } }, - "/ai-assistant/memory-bank": { + "/ai-assistant/{conversation_id}/decline": { "post": { - "description": "Generate a memory bank configuration suggestion via the AI assistant. The AI proposes name, type, mode, compaction prompt, and retention settings.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_memory_bank_api_ai_assistant_memory_bank_post", + "description": "Decline a previously proposed standalone plan. No resources are modified. The conversation is marked as declined.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", + "operationId": "api_ai_decline_api_ai_assistant__conversation_id__decline_post", "parameters": [ + { + "in": "path", + "name": "conversation_id", + "required": true, + "schema": { + "format": "uuid", + "title": "Conversation Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankAiAssistantRequest" + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } + }, + "description": "Validation Error" + } + }, + "summary": "Decline a standalone plan", + "tags": [ + "ai-assistant" + ] + } + }, + "/alerts": { + "get": { + "description": "List alerts for the account with optional filters.\n\nFilters:\n- `status`: triggered, acknowledged, resolved, dismissed\n- `agent_id`: filter by agent\n- `source_connection_id`: filter by source\n- `time_from` / `time_to`: ISO 8601 date range\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Results are scoped to the caller's account.", + "operationId": "list_alerts_api_alerts_get", + "parameters": [ + { + "description": "Page number", + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "description": "Page number", + "minimum": 1, + "title": "Page", + "type": "integer" } }, - "required": true - }, + { + "description": "Items per page", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 20, + "description": "Items per page", + "maximum": 100, + "minimum": 1, + "title": "Limit", + "type": "integer" + } + }, + { + "description": "Filter by alert status", + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by alert status", + "title": "Status" + } + }, + { + "description": "Filter by agent ID", + "in": "query", + "name": "agent_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by agent ID", + "title": "Agent Id" + } + }, + { + "description": "Filter by source connection ID", + "in": "query", + "name": "source_connection_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by source connection ID", + "title": "Source Connection Id" + } + }, + { + "description": "From (ISO 8601)", + "in": "query", + "name": "time_from", + "required": false, + "schema": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "From (ISO 8601)", + "title": "Time From" + } + }, + { + "description": "To (ISO 8601)", + "in": "query", + "name": "time_to", + "required": false, + "schema": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "To (ISO 8601)", + "title": "Time To" + } + }, + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MemoryBankAiAssistantResponse" + "additionalProperties": true, + "title": "Response List Alerts Api Alerts Get", + "type": "object" } } }, @@ -11215,42 +13440,69 @@ "description": "Validation Error" } }, - "summary": "Generate a memory bank configuration (standalone)", + "summary": "List alerts", "tags": [ - "ai-assistant" + "alerts" ] } }, - "/ai-assistant/memory-bank/last-conversation": { + "/alerts/configs": { "get": { - "description": "Fetch the most recent memory bank AI assistant conversation turns for the authenticated user. Returns turns in oldest-first order with a total count for pagination via limit/offset query parameters.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_memory_bank_history_api_ai_assistant_memory_bank_last_conversation_get", + "description": "List alert configurations.\n\nFilters:\n- `agent_id`: list configs for a specific agent\n- `source_connection_id`: list configs for a specific source\n- Neither: list account-level agent alert configs\n- `scope=source`: list account-level source alert configs\n\nCredits alerts (`credits_low_threshold`, `credits_runout_prediction`, `credits_usage_spike`) are account-level alert configs. They are evaluated by the credits alert sweep and default-enabled configs may be auto-created for active accounts at runtime.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "list_alert_configs_api_alerts_configs_get", "parameters": [ { - "description": "Max turns.", + "description": "Filter by agent ID", "in": "query", - "name": "limit", + "name": "agent_id", "required": false, "schema": { - "default": 5, - "description": "Max turns.", - "maximum": 50, - "minimum": 1, - "title": "Limit", - "type": "integer" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by agent ID", + "title": "Agent Id" + } + }, + { + "description": "Filter by source connection ID", + "in": "query", + "name": "source_connection_id", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by source connection ID", + "title": "Source Connection Id" } }, { - "description": "Skip count.", + "description": "Set to 'source' to list account-level source alert configs", "in": "query", - "name": "offset", + "name": "scope", "required": false, "schema": { - "default": 0, - "description": "Skip count.", - "minimum": 0, - "title": "Offset", - "type": "integer" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Set to 'source' to list account-level source alert configs", + "title": "Scope" } }, { @@ -11262,7 +13514,9 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankLastConversationResponse" + "additionalProperties": true, + "title": "Response List Alert Configs Api Alerts Configs Get", + "type": "object" } } }, @@ -11279,27 +13533,15 @@ "description": "Validation Error" } }, - "summary": "Fetch memory bank AI conversation history", + "summary": "List alert configs", "tags": [ - "ai-assistant" + "alerts" ] - } - }, - "/ai-assistant/memory-bank/{conversation_id}": { - "patch": { - "description": "Update the acceptance status of a memory bank AI assistant conversation turn. Set ``accepted`` to true to accept the proposed configuration, or false to decline it. The accepted status is recorded for audit purposes.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_memory_bank_accept_api_ai_assistant_memory_bank__conversation_id__patch", + }, + "post": { + "description": "Create a new alert configuration.\n\nAgent alert types: run_failed, consecutive_failures, error_rate_spike, run_burst, slow_run, credits_low_threshold, credits_runout_prediction, credits_usage_spike, non_manual_eval_failed, non_manual_eval_flagged, governance_flagged, governance_blocked, model_newer_available, model_deprecated, model_sunset.\nSource alert types: pull_failed, consecutive_pull_failures, pull_error_rate_spike.\n\nDistribution types: owner, owner_admins, selected_members. Organization accounts are normalized to owner_admins.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "create_alert_config_api_alerts_configs_post", "parameters": [ - { - "in": "path", - "name": "conversation_id", - "required": true, - "schema": { - "format": "uuid", - "title": "Conversation Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -11308,21 +13550,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__memory_banks__MemoryBankAcceptRequest" + "$ref": "#/components/schemas/CreateAlertConfigRequest" } } }, "required": true }, "responses": { - "200": { + "201": { "content": { "application/json": { "schema": { - "additionalProperties": { - "type": "boolean" - }, - "title": "Response Api Ai Memory Bank Accept Api Ai Assistant Memory Bank Conversation Id Patch", + "additionalProperties": true, + "title": "Response Create Alert Config Api Alerts Configs Post", "type": "object" } } @@ -11340,40 +13580,32 @@ "description": "Validation Error" } }, - "summary": "Accept or decline a memory bank AI suggestion", + "summary": "Create alert config", "tags": [ - "ai-assistant" + "alerts" ] } }, - "/ai-assistant/solution": { - "post": { - "description": "Generate a complete solution plan covering sources, knowledge bases, and agents without requiring an existing solution. Supports SSE streaming when ``Accept: text/event-stream`` is set.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_solution_api_ai_assistant_solution_post", + "/alerts/configs/{config_id}": { + "delete": { + "description": "Delete an alert configuration. This permanently removes the config and stops any future alerts of this type from being triggered.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "delete_alert_config_api_alerts_configs__config_id__delete", "parameters": [ + { + "in": "path", + "name": "config_id", + "required": true, + "schema": { + "title": "Config Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" - } - } - }, - "required": true - }, "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AiAssistantGenerateResponse" - } - } - }, + "204": { "description": "Successful Response" }, "422": { @@ -11387,37 +13619,36 @@ "description": "Validation Error" } }, - "summary": "Generate a solution plan (standalone)", + "summary": "Delete alert config", "tags": [ - "ai-assistant" + "alerts" ] - } - }, - "/ai-assistant/source": { - "post": { - "description": "Generate a content source creation/modification plan without requiring an existing solution. The AI proposes actions for the user to review before any changes are made.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_source_api_ai_assistant_source_post", + }, + "get": { + "description": "Get a specific alert configuration by ID.\n\nReturns all fields including type, enabled state, threshold, cooldown, distribution type, and recipient list.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "get_alert_config_api_alerts_configs__config_id__get", "parameters": [ + { + "in": "path", + "name": "config_id", + "required": true, + "schema": { + "title": "Config Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/routers__api__solutions__AiAssistantGenerateRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiAssistantGenerateResponse" + "additionalProperties": true, + "title": "Response Get Alert Config Api Alerts Configs Config Id Get", + "type": "object" } } }, @@ -11434,24 +13665,21 @@ "description": "Validation Error" } }, - "summary": "Generate a source plan (standalone)", + "summary": "Get alert config", "tags": [ - "ai-assistant" + "alerts" ] - } - }, - "/ai-assistant/{conversation_id}/accept": { - "post": { - "description": "Accept and execute a previously proposed standalone plan. If the plan contains destructive actions (deletions), ``confirm_deletions`` must be set to true.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_accept_api_ai_assistant__conversation_id__accept_post", + }, + "patch": { + "description": "Update an alert configuration. Only provided fields are updated.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "update_alert_config_api_alerts_configs__config_id__patch", "parameters": [ { "in": "path", - "name": "conversation_id", + "name": "config_id", "required": true, "schema": { - "format": "uuid", - "title": "Conversation Id", + "title": "Config Id", "type": "string" } }, @@ -11463,7 +13691,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__solutions__AiAssistantAcceptRequest" + "$ref": "#/components/schemas/UpdateAlertConfigRequest" } } }, @@ -11474,50 +13702,12 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AiAssistantAcceptResponse" - } - } - }, - "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "additionalProperties": true, + "title": "Response Update Alert Config Api Alerts Configs Config Id Patch", + "type": "object" } } }, - "description": "Validation Error" - } - }, - "summary": "Accept and execute a standalone plan", - "tags": [ - "ai-assistant" - ] - } - }, - "/ai-assistant/{conversation_id}/decline": { - "post": { - "description": "Decline a previously proposed standalone plan. No resources are modified. The conversation is marked as declined.\n\nAuth: requires ``X-API-Key`` header or OAuth Bearer token.", - "operationId": "api_ai_decline_api_ai_assistant__conversation_id__decline_post", - "parameters": [ - { - "in": "path", - "name": "conversation_id", - "required": true, - "schema": { - "format": "uuid", - "title": "Conversation Id", - "type": "string" - } - }, - { - "$ref": "#/components/parameters/X-Account-Id" - } - ], - "responses": { - "204": { "description": "Successful Response" }, "422": { @@ -11531,148 +13721,124 @@ "description": "Validation Error" } }, - "summary": "Decline a standalone plan", + "summary": "Update alert config", "tags": [ - "ai-assistant" + "alerts" ] } }, - "/alerts": { + "/alerts/organization-preferences/list": { "get": { - "description": "List alerts for the account with optional filters.\n\nFilters:\n- `status`: triggered, acknowledged, resolved, dismissed\n- `agent_id`: filter by agent\n- `source_connection_id`: filter by source\n- `time_from` / `time_to`: ISO 8601 date range\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Results are scoped to the caller's account.", - "operationId": "list_alerts_api_alerts_get", - "parameters": [ - { - "description": "Page number", - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "description": "Page number", - "minimum": 1, - "title": "Page", - "type": "integer" - } - }, - { - "description": "Items per page", - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 20, - "description": "Items per page", - "maximum": 100, - "minimum": 1, - "title": "Limit", - "type": "integer" - } - }, - { - "description": "Filter by alert status", - "in": "query", - "name": "status", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Filter by alert status", - "title": "Status" - } - }, + "description": "List per-organization alert delivery preferences for the authenticated user.\n\nBy default, only explicit override rows are returned. Set `include_defaults=true` to return the effective subscribed state for every alert type in every organization the user can manage.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.\n- Only organizations where the user is an owner or administrator are included.", + "operationId": "list_organization_preferences_api_alerts_organization_preferences_list_get", + "parameters": [ { - "description": "Filter by agent ID", + "description": "Optional organization filter", "in": "query", - "name": "agent_id", + "name": "organization_id", "required": false, "schema": { "anyOf": [ { + "format": "uuid", "type": "string" }, { "type": "null" } ], - "description": "Filter by agent ID", - "title": "Agent Id" + "description": "Optional organization filter", + "title": "Organization Id" } }, { - "description": "Filter by source connection ID", + "description": "Include default subscribed entries for all alert types", "in": "query", - "name": "source_connection_id", + "name": "include_defaults", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Filter by source connection ID", - "title": "Source Connection Id" + "default": false, + "description": "Include default subscribed entries for all alert types", + "title": "Include Defaults", + "type": "boolean" } }, { - "description": "From (ISO 8601)", - "in": "query", - "name": "time_from", - "required": false, - "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationAlertPreferenceListResponse" } - ], - "description": "From (ISO 8601)", - "title": "Time From" + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "List organization alert delivery preferences", + "tags": [ + "alerts" + ] + } + }, + "/alerts/organization-preferences/{organization_id}/{alert_type}": { + "patch": { + "description": "Update the authenticated user's personal delivery preference for one alert type in one organization.\n\nSetting `subscribed=false` stores an explicit opt-out override. Setting `subscribed=true` removes the override and restores the default subscribed behavior.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.\n- Only owners and administrators can update preferences for an organization.", + "operationId": "update_organization_preference_api_alerts_organization_preferences__organization_id___alert_type__patch", + "parameters": [ + { + "in": "path", + "name": "organization_id", + "required": true, + "schema": { + "format": "uuid", + "title": "Organization Id", + "type": "string" } }, { - "description": "To (ISO 8601)", - "in": "query", - "name": "time_to", - "required": false, + "in": "path", + "name": "alert_type", + "required": true, "schema": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "null" - } - ], - "description": "To (ISO 8601)", - "title": "Time To" + "title": "Alert Type", + "type": "string" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__alerts__UpdateOrganizationAlertPreferenceRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "additionalProperties": true, - "title": "Response List Alerts Api Alerts Get", - "type": "object" + "$ref": "#/components/schemas/routers__api__alerts__OrganizationAlertPreferenceResponse" } } }, @@ -11689,82 +13855,95 @@ "description": "Validation Error" } }, - "summary": "List alerts", + "summary": "Update organization alert delivery preference", "tags": [ "alerts" ] } }, - "/alerts/configs": { + "/alerts/{alert_id}": { "get": { - "description": "List alert configurations.\n\nFilters:\n- `agent_id`: list configs for a specific agent\n- `source_connection_id`: list configs for a specific source\n- Neither: list account-level agent alert configs\n- `scope=source`: list account-level source alert configs\n\nCredits alerts (`credits_low_threshold`, `credits_runout_prediction`, `credits_usage_spike`) are account-level alert configs. They are evaluated by the credits alert sweep and default-enabled configs may be auto-created for active accounts at runtime.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "list_alert_configs_api_alerts_configs_get", + "description": "Get full alert detail including history, comments, and subscribers.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "get_alert_detail_api_alerts__alert_id__get", "parameters": [ { - "description": "Filter by agent ID", - "in": "query", - "name": "agent_id", - "required": false, + "in": "path", + "name": "alert_id", + "required": true, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Filter by agent ID", - "title": "Agent Id" + "title": "Alert Id", + "type": "string" } }, { - "description": "Filter by source connection ID", - "in": "query", - "name": "source_connection_id", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "title": "Response Get Alert Detail Api Alerts Alert Id Get", + "type": "object" } - ], - "description": "Filter by source connection ID", - "title": "Source Connection Id" - } + } + }, + "description": "Successful Response" }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Get alert detail", + "tags": [ + "alerts" + ] + } + }, + "/alerts/{alert_id}/comments": { + "post": { + "description": "Add a comment to an alert. Comments are visible to all subscribers and are included in the alert detail response.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "add_alert_comment_api_alerts__alert_id__comments_post", + "parameters": [ { - "description": "Set to 'source' to list account-level source alert configs", - "in": "query", - "name": "scope", - "required": false, + "in": "path", + "name": "alert_id", + "required": true, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Set to 'source' to list account-level source alert configs", - "title": "Scope" + "title": "Alert Id", + "type": "string" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/routers__api__alerts__AddCommentRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { "additionalProperties": true, - "title": "Response List Alert Configs Api Alerts Configs Get", + "title": "Response Add Alert Comment Api Alerts Alert Id Comments Post", "type": "object" } } @@ -11782,15 +13961,26 @@ "description": "Validation Error" } }, - "summary": "List alert configs", + "summary": "Add alert comment", "tags": [ "alerts" ] - }, + } + }, + "/alerts/{alert_id}/status": { "post": { - "description": "Create a new alert configuration.\n\nAgent alert types: run_failed, consecutive_failures, error_rate_spike, run_burst, slow_run, credits_low_threshold, credits_runout_prediction, credits_usage_spike, non_manual_eval_failed, non_manual_eval_flagged, governance_flagged, governance_blocked, model_newer_available, model_deprecated, model_sunset.\nSource alert types: pull_failed, consecutive_pull_failures, pull_error_rate_spike.\n\nDistribution types: owner, owner_admins, selected_members. Organization accounts are normalized to owner_admins.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "create_alert_config_api_alerts_configs_post", + "description": "Change the status of an alert. Valid statuses: triggered, acknowledged, resolved, dismissed.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "change_alert_status_api_alerts__alert_id__status_post", "parameters": [ + { + "in": "path", + "name": "alert_id", + "required": true, + "schema": { + "title": "Alert Id", + "type": "string" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -11799,19 +13989,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateAlertConfigRequest" + "$ref": "#/components/schemas/ChangeStatusRequest" } } }, "required": true }, "responses": { - "201": { + "200": { "content": { "application/json": { "schema": { "additionalProperties": true, - "title": "Response Create Alert Config Api Alerts Configs Post", + "title": "Response Change Alert Status Api Alerts Alert Id Status Post", "type": "object" } } @@ -11829,23 +14019,23 @@ "description": "Validation Error" } }, - "summary": "Create alert config", + "summary": "Change alert status", "tags": [ "alerts" ] } }, - "/alerts/configs/{config_id}": { - "delete": { - "description": "Delete an alert configuration. This permanently removes the config and stops any future alerts of this type from being triggered.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "delete_alert_config_api_alerts_configs__config_id__delete", + "/alerts/{alert_id}/subscribe": { + "post": { + "description": "Subscribe the current user to an alert. Subscribed users receive email notifications when the alert status changes or new comments are added.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "subscribe_to_alert_api_alerts__alert_id__subscribe_post", "parameters": [ { "in": "path", - "name": "config_id", + "name": "alert_id", "required": true, "schema": { - "title": "Config Id", + "title": "Alert Id", "type": "string" } }, @@ -11854,7 +14044,16 @@ } ], "responses": { - "204": { + "200": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "title": "Response Subscribe To Alert Api Alerts Alert Id Subscribe Post", + "type": "object" + } + } + }, "description": "Successful Response" }, "422": { @@ -11868,21 +14067,23 @@ "description": "Validation Error" } }, - "summary": "Delete alert config", + "summary": "Subscribe to alert", "tags": [ "alerts" ] - }, - "get": { - "description": "Get a specific alert configuration by ID.\n\nReturns all fields including type, enabled state, threshold, cooldown, distribution type, and recipient list.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "get_alert_config_api_alerts_configs__config_id__get", + } + }, + "/alerts/{alert_id}/unsubscribe": { + "post": { + "description": "Unsubscribe the current user from an alert. The user will no longer receive email notifications for status changes or new comments on this alert.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "unsubscribe_from_alert_api_alerts__alert_id__unsubscribe_post", "parameters": [ { "in": "path", - "name": "config_id", + "name": "alert_id", "required": true, "schema": { - "title": "Config Id", + "title": "Alert Id", "type": "string" } }, @@ -11896,7 +14097,7 @@ "application/json": { "schema": { "additionalProperties": true, - "title": "Response Get Alert Config Api Alerts Configs Config Id Get", + "title": "Response Unsubscribe From Alert Api Alerts Alert Id Unsubscribe Post", "type": "object" } } @@ -11914,21 +14115,23 @@ "description": "Validation Error" } }, - "summary": "Get alert config", + "summary": "Unsubscribe from alert", "tags": [ "alerts" ] - }, - "patch": { - "description": "Update an alert configuration. Only provided fields are updated.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "update_alert_config_api_alerts_configs__config_id__patch", + } + }, + "/contents/{source_connection_content_version}": { + "delete": { + "description": "Delete a content item (a `SourceConnectionContentVersion`).\n\nUse this to remove an uploaded or indexed item from your account. Deleting content can affect agents and knowledge base workflows that reference this item.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only delete content belonging to your account.", + "operationId": "delete_content_api_contents__source_connection_content_version__delete", "parameters": [ { "in": "path", - "name": "config_id", + "name": "source_connection_content_version", "required": true, "schema": { - "title": "Config Id", + "title": "Source Connection Content Version", "type": "string" } }, @@ -11936,27 +14139,8 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateAlertConfigRequest" - } - } - }, - "required": true - }, "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": true, - "title": "Response Update Alert Config Api Alerts Configs Config Id Patch", - "type": "object" - } - } - }, + "204": { "description": "Successful Response" }, "422": { @@ -11970,46 +14154,42 @@ "description": "Validation Error" } }, - "summary": "Update alert config", + "summary": "Delete content", "tags": [ - "alerts" + "contents" ] - } - }, - "/alerts/organization-preferences/list": { + }, "get": { - "description": "List per-organization alert delivery preferences for the authenticated user.\n\nBy default, only explicit override rows are returned. Set `include_defaults=true` to return the effective subscribed state for every alert type in every organization the user can manage.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.\n- Only organizations where the user is an owner or administrator are included.", - "operationId": "list_organization_preferences_api_alerts_organization_preferences_list_get", + "description": "Get detailed information about a specific content item (a `SourceConnectionContentVersion`).\n\nThis is useful when you want to:\n- Inspect the extracted text for debugging or review.\n- Display content details in a UI.\n\nText range:\n- `start` and `end` control the character range returned in `text_content` so clients can page through large documents.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access content belonging to your account.", + "operationId": "get_content_detail_api_contents__source_connection_content_version__get", "parameters": [ { - "description": "Optional organization filter", + "in": "path", + "name": "source_connection_content_version", + "required": true, + "schema": { + "title": "Source Connection Content Version", + "type": "string" + } + }, + { "in": "query", - "name": "organization_id", + "name": "start", "required": false, "schema": { - "anyOf": [ - { - "format": "uuid", - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional organization filter", - "title": "Organization Id" + "default": 0, + "title": "Start", + "type": "integer" } }, { - "description": "Include default subscribed entries for all alert types", "in": "query", - "name": "include_defaults", + "name": "end", "required": false, "schema": { - "default": false, - "description": "Include default subscribed entries for all alert types", - "title": "Include Defaults", - "type": "boolean" + "default": 5000, + "title": "End", + "type": "integer" } }, { @@ -12021,7 +14201,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OrganizationAlertPreferenceListResponse" + "$ref": "#/components/schemas/routers__api__contents__ContentDetailResponse" } } }, @@ -12038,33 +14218,21 @@ "description": "Validation Error" } }, - "summary": "List organization alert delivery preferences", + "summary": "Get content details", "tags": [ - "alerts" + "contents" ] - } - }, - "/alerts/organization-preferences/{organization_id}/{alert_type}": { - "patch": { - "description": "Update the authenticated user's personal delivery preference for one alert type in one organization.\n\nSetting `subscribed=false` stores an explicit opt-out override. Setting `subscribed=true` removes the override and restores the default subscribed behavior.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.\n- Only owners and administrators can update preferences for an organization.", - "operationId": "update_organization_preference_api_alerts_organization_preferences__organization_id___alert_type__patch", + }, + "put": { + "description": "Replace a content version using a small inline text payload.\n\n**Maximum payload size:** 8192 bytes (UTF-8).\n\n**Supported content types:**\n- `application/json`\n- `application/xml`\n- `text/csv`\n- `text/html`\n- `text/markdown`\n- `text/plain`\n- `text/x-markdown`\n- `text/xml`\n\nNotes:\n- Use this endpoint for small text payloads; larger files should use `/upload`.\n- `title` is merged into `metadata.title` when not already present.", + "operationId": "replace_content_with_inline_text_api_contents__source_connection_content_version__put", "parameters": [ { "in": "path", - "name": "organization_id", - "required": true, - "schema": { - "format": "uuid", - "title": "Organization Id", - "type": "string" - } - }, - { - "in": "path", - "name": "alert_type", + "name": "source_connection_content_version", "required": true, "schema": { - "title": "Alert Type", + "title": "Source Connection Content Version", "type": "string" } }, @@ -12076,7 +14244,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__alerts__UpdateOrganizationAlertPreferenceRequest" + "$ref": "#/components/schemas/InlineTextReplaceRequest" } } }, @@ -12087,7 +14255,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__alerts__OrganizationAlertPreferenceResponse" + "$ref": "#/components/schemas/routers__api__contents__FileUploadResponse" } } }, @@ -12104,26 +14272,46 @@ "description": "Validation Error" } }, - "summary": "Update organization alert delivery preference", + "summary": "Replace a content version with inline text", "tags": [ - "alerts" + "contents" ] } }, - "/alerts/{alert_id}": { + "/contents/{source_connection_content_version}/embeddings": { "get": { - "description": "Get full alert detail including history, comments, and subscribers.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "get_alert_detail_api_alerts__alert_id__get", + "description": "List the embeddings (chunk vectors) for a content item, with pagination.\n\nEmbeddings are used for semantic search and retrieval in knowledge base workflows. This endpoint is primarily useful for debugging chunking, indexing, and vector contents.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access embeddings for content belonging to your account.", + "operationId": "list_content_embeddings_api_contents__source_connection_content_version__embeddings_get", "parameters": [ { "in": "path", - "name": "alert_id", + "name": "source_connection_content_version", "required": true, "schema": { - "title": "Alert Id", + "title": "Source Connection Content Version", "type": "string" } }, + { + "in": "query", + "name": "page", + "required": false, + "schema": { + "default": 1, + "title": "Page", + "type": "integer" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 20, + "title": "Limit", + "type": "integer" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -12133,9 +14321,7 @@ "content": { "application/json": { "schema": { - "additionalProperties": true, - "title": "Response Get Alert Detail Api Alerts Alert Id Get", - "type": "object" + "$ref": "#/components/schemas/routers__api__contents__ContentEmbeddingsListResponse" } } }, @@ -12152,23 +14338,23 @@ "description": "Validation Error" } }, - "summary": "Get alert detail", + "summary": "List content embeddings", "tags": [ - "alerts" + "contents" ] } }, - "/alerts/{alert_id}/comments": { + "/contents/{source_connection_content_version}/upload": { "post": { - "description": "Add a comment to an alert. Comments are visible to all subscribers and are included in the alert detail response.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "add_alert_comment_api_alerts__alert_id__comments_post", + "description": "Upload a new file and replace the content backing an existing `SourceConnectionContentVersion`.\n\nThis behaves like a source file upload, but it targets an existing content version ID. This is useful when you want to correct or update an uploaded document while keeping references stable.\n\n**Maximum file size:** 209715200 bytes.\n\n**Supported MIME types:**\n- `application/epub+zip`\n- `application/json`\n- `application/pdf`\n- `application/vnd.ms-excel`\n- `application/vnd.ms-outlook`\n- `application/vnd.ms-powerpoint`\n- `application/vnd.openxmlformats-officedocument.presentationml.presentation`\n- `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`\n- `application/vnd.openxmlformats-officedocument.wordprocessingml.document`\n- `application/xml`\n- `application/zip`\n- `audio/flac`\n- `audio/mp4`\n- `audio/mpeg`\n- `audio/ogg`\n- `audio/wav`\n- `image/bmp`\n- `image/gif`\n- `image/jpeg`\n- `image/png`\n- `image/svg+xml`\n- `image/tiff`\n- `image/webp`\n- `text/csv`\n- `text/html`\n- `text/markdown`\n- `text/plain`\n- `text/x-markdown`\n- `text/xml`\n- `video/mp4`\n- `video/quicktime`\n- `video/x-msvideo`\n\nNotes:\n- If the uploaded file's content type is `application/octet-stream`, the server attempts to infer the type from the file extension.\n- Use `metadata` to attach an arbitrary JSON object of metadata (for example `metadata={\"category\":\"docs\"}`).\n- `title` is a convenience field and is merged into the metadata as `metadata.title` (it does not override an existing `metadata.title`).\n- For backwards compatibility, you can also pass form fields named `metadata_` (for example `metadata_author=...`). These override keys from `metadata`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only replace content belonging to your account.", + "operationId": "upload_file_to_content_api_contents__source_connection_content_version__upload_post", "parameters": [ { "in": "path", - "name": "alert_id", + "name": "source_connection_content_version", "required": true, "schema": { - "title": "Alert Id", + "title": "Source Connection Content Version", "type": "string" } }, @@ -12178,9 +14364,9 @@ ], "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/routers__api__alerts__AddCommentRequest" + "$ref": "#/components/schemas/Body_upload_file_to_content_api_contents__source_connection_content_version__upload_post" } } }, @@ -12191,9 +14377,7 @@ "content": { "application/json": { "schema": { - "additionalProperties": true, - "title": "Response Add Alert Comment Api Alerts Alert Id Comments Post", - "type": "object" + "$ref": "#/components/schemas/routers__api__contents__FileUploadResponse" } } }, @@ -12210,47 +14394,71 @@ "description": "Validation Error" } }, - "summary": "Add alert comment", + "summary": "Replace a content version with a new upload", "tags": [ - "alerts" + "contents" ] } }, - "/alerts/{alert_id}/status": { - "post": { - "description": "Change the status of an alert. Valid statuses: triggered, acknowledged, resolved, dismissed.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "change_alert_status_api_alerts__alert_id__status_post", + "/docs-search": { + "get": { + "description": "Search the Seclai documentation by content and return matching pages. `mode=keyword` matches page titles and summaries (fast, no AI cost); `mode=semantic` matches page body content by meaning (uses an embedding). Each result carries a `doc_slug` and an optional section `anchor` for building a `https://seclai.com/docs/[#]` link, a `score` (relevance; not comparable across modes), and \u2014 in semantic mode \u2014 a `highlight` (best matching verbatim sentence; `null` for keyword). Documentation is global, so results are not account-scoped.", + "operationId": "docs_search_api_docs_search_get", "parameters": [ { - "in": "path", - "name": "alert_id", + "description": "Search query", + "in": "query", + "name": "q", "required": true, "schema": { - "title": "Alert Id", - "type": "string" + "description": "Search query", + "maxLength": 200, + "minLength": 1, + "title": "Q", + "type": "string" + } + }, + { + "description": "Search strategy", + "in": "query", + "name": "mode", + "required": false, + "schema": { + "default": "keyword", + "description": "Search strategy", + "enum": [ + "keyword", + "semantic" + ], + "title": "Mode", + "type": "string" + } + }, + { + "description": "Maximum results", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 8, + "description": "Maximum results", + "maximum": 20, + "minimum": 1, + "title": "Limit", + "type": "integer" } }, { "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChangeStatusRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { "additionalProperties": true, - "title": "Response Change Alert Status Api Alerts Alert Id Status Post", + "title": "Response Docs Search Api Docs Search Get", "type": "object" } } @@ -12268,26 +14476,17 @@ "description": "Validation Error" } }, - "summary": "Change alert status", + "summary": "Search documentation", "tags": [ - "alerts" + "search" ] } }, - "/alerts/{alert_id}/subscribe": { - "post": { - "description": "Subscribe the current user to an alert. Subscribed users receive email notifications when the alert status changes or new comments are added.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "subscribe_to_alert_api_alerts__alert_id__subscribe_post", + "/email-domains": { + "get": { + "description": "List the account's vanity (`.seclai.com`) and custom (`agent.mycompany.com`) agent-email domains with their verification status, the DNS records the customer must publish, and the plan capabilities (`can_add_vanity`/`can_add_custom`) plus whether one of each kind already exists (`has_vanity`/`has_custom` \u2014 the per-kind limit is 1).\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "list_email_domains_api_api_email_domains_get", "parameters": [ - { - "in": "path", - "name": "alert_id", - "required": true, - "schema": { - "title": "Alert Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -12297,57 +14496,42 @@ "content": { "application/json": { "schema": { - "additionalProperties": true, - "title": "Response Subscribe To Alert Api Alerts Alert Id Subscribe Post", - "type": "object" + "$ref": "#/components/schemas/EmailDomainsListResponse" } } }, "description": "Successful Response" - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error" } }, - "summary": "Subscribe to alert", + "summary": "List the account's email domains + plan capabilities", "tags": [ - "alerts" + "email-domains" ] - } - }, - "/alerts/{alert_id}/unsubscribe": { + }, "post": { - "description": "Unsubscribe the current user from an alert. The user will no longer receive email notifications for status changes or new comments on this alert.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", - "operationId": "unsubscribe_from_alert_api_alerts__alert_id__unsubscribe_post", + "description": "Add a new vanity subdomain (`kind=vanity`, `value=`) or custom domain (`kind=custom`, `value=agent.mycompany.com`; optional `delegated=true` to let Seclai manage a dedicated Route53 zone). Stands up the SES identity + DNS and returns the records the customer must publish. Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "add_email_domain_api_api_email_domains_post", "parameters": [ - { - "in": "path", - "name": "alert_id", - "required": true, - "schema": { - "title": "Alert Id", - "type": "string" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddEmailDomainRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { "schema": { - "additionalProperties": true, - "title": "Response Unsubscribe From Alert Api Alerts Alert Id Unsubscribe Post", - "type": "object" + "$ref": "#/components/schemas/EmailDomainResponse" } } }, @@ -12364,23 +14548,44 @@ "description": "Validation Error" } }, - "summary": "Unsubscribe from alert", + "summary": "Add + provision an email domain", "tags": [ - "alerts" + "email-domains" ] } }, - "/contents/{source_connection_content_version}": { + "/email-domains/use-shared-domain": { + "post": { + "description": "Clear the account's primary domain so agent email reverts to the shared `agent.seclai.com` sending/inbound scheme, WITHOUT removing the configured domain(s) \u2014 they stay verified and can be promoted again later. Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "use_shared_domain_api_api_email_domains_use_shared_domain_post", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "204": { + "description": "Successful Response" + } + }, + "summary": "Revert to the shared agent.seclai.com sending domain", + "tags": [ + "email-domains" + ] + } + }, + "/email-domains/{domain_id}": { "delete": { - "description": "Delete a content item (a `SourceConnectionContentVersion`).\n\nUse this to remove an uploaded or indexed item from your account. Deleting content can affect agents and knowledge base workflows that reference this item.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only delete content belonging to your account.", - "operationId": "delete_content_api_contents__source_connection_content_version__delete", + "description": "Remove a domain and tear down its SES identity + DNS / receipt-rule recipient. Returns a `cleanup_note` when the removed domain was Seclai-managed (delegated), reminding the caller to delete the registrar NS delegation record (dangling-delegation / subdomain-takeover guard). Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "remove_email_domain_api_api_email_domains__domain_id__delete", "parameters": [ { "in": "path", - "name": "source_connection_content_version", + "name": "domain_id", "required": true, "schema": { - "title": "Source Connection Content Version", + "format": "uuid", + "title": "Domain Id", "type": "string" } }, @@ -12389,7 +14594,14 @@ } ], "responses": { - "204": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoveEmailDomainResponse" + } + } + }, "description": "Successful Response" }, "422": { @@ -12403,41 +14615,44 @@ "description": "Validation Error" } }, - "summary": "Delete content", + "summary": "Remove an email domain", "tags": [ - "contents" + "email-domains" ] - }, + } + }, + "/email-domains/{domain_id}/dmarc": { "get": { - "description": "Get detailed information about a specific content item (a `SourceConnectionContentVersion`).\n\nThis is useful when you want to:\n- Inspect the extracted text for debugging or review.\n- Display content details in a UI.\n\nText range:\n- `start` and `end` control the character range returned in `text_content` so clients can page through large documents.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access content belonging to your account.", - "operationId": "get_content_detail_api_contents__source_connection_content_version__get", + "description": "Pass rate, disposition breakdown (`none`/`quarantine`/`reject`), and top failing source IPs from the DMARC `rua` aggregate reports over the last `days` (clamped by the service). Populated for domains whose DNS zone Seclai controls (vanity + delegated custom); a self-managed custom domain keeps its own DMARC reporting and returns an all-zero summary.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "get_dmarc_summary_api_api_email_domains__domain_id__dmarc_get", "parameters": [ { "in": "path", - "name": "source_connection_content_version", + "name": "domain_id", "required": true, "schema": { - "title": "Source Connection Content Version", + "format": "uuid", + "title": "Domain Id", "type": "string" } }, { "in": "query", - "name": "start", + "name": "days", "required": false, "schema": { - "default": 0, - "title": "Start", + "default": 30, + "title": "Days", "type": "integer" } }, { "in": "query", - "name": "end", + "name": "top_sources", "required": false, "schema": { - "default": 5000, - "title": "End", + "default": 10, + "title": "Top Sources", "type": "integer" } }, @@ -12450,7 +14665,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__contents__ContentDetailResponse" + "$ref": "#/components/schemas/DmarcSummaryResponse" } } }, @@ -12467,21 +14682,24 @@ "description": "Validation Error" } }, - "summary": "Get content details", + "summary": "DMARC aggregate-report summary for a domain", "tags": [ - "contents" + "email-domains" ] - }, - "put": { - "description": "Replace a content version using a small inline text payload.\n\n**Maximum payload size:** 8192 bytes (UTF-8).\n\n**Supported content types:**\n- `application/json`\n- `application/xml`\n- `text/csv`\n- `text/html`\n- `text/markdown`\n- `text/plain`\n- `text/x-markdown`\n- `text/xml`\n\nNotes:\n- Use this endpoint for small text payloads; larger files should use `/upload`.\n- `title` is merged into `metadata.title` when not already present.", - "operationId": "replace_content_with_inline_text_api_contents__source_connection_content_version__put", + } + }, + "/email-domains/{domain_id}/primary": { + "post": { + "description": "Promote a verified domain to the account's primary domain \u2014 agent email then sends FROM and receives ON this domain (`@`, `@`) instead of the shared `agent.seclai.com`. The domain must be verified. Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "set_primary_email_domain_api_api_email_domains__domain_id__primary_post", "parameters": [ { "in": "path", - "name": "source_connection_content_version", + "name": "domain_id", "required": true, "schema": { - "title": "Source Connection Content Version", + "format": "uuid", + "title": "Domain Id", "type": "string" } }, @@ -12489,22 +14707,12 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InlineTextReplaceRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__contents__FileUploadResponse" + "$ref": "#/components/schemas/EmailDomainResponse" } } }, @@ -12521,46 +14729,27 @@ "description": "Validation Error" } }, - "summary": "Replace a content version with inline text", + "summary": "Make a verified domain the account's primary sending/inbound domain", "tags": [ - "contents" + "email-domains" ] } }, - "/contents/{source_connection_content_version}/embeddings": { - "get": { - "description": "List the embeddings (chunk vectors) for a content item, with pagination.\n\nEmbeddings are used for semantic search and retrieval in knowledge base workflows. This endpoint is primarily useful for debugging chunking, indexing, and vector contents.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only access embeddings for content belonging to your account.", - "operationId": "list_content_embeddings_api_contents__source_connection_content_version__embeddings_get", + "/email-domains/{domain_id}/test-email": { + "post": { + "description": "Send a test message FROM the verified domain (`noreply@`) TO the account owner's email, to confirm end-to-end that the domain actually sends (SES verified, DKIM/SPF aligned). Never sends to any other address, so it can't be used as an open relay. Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "send_test_email_api_api_email_domains__domain_id__test_email_post", "parameters": [ { "in": "path", - "name": "source_connection_content_version", + "name": "domain_id", "required": true, "schema": { - "title": "Source Connection Content Version", + "format": "uuid", + "title": "Domain Id", "type": "string" } }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "title": "Page", - "type": "integer" - } - }, - { - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 20, - "title": "Limit", - "type": "integer" - } - }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -12570,7 +14759,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__contents__ContentEmbeddingsListResponse" + "$ref": "#/components/schemas/SendTestEmailResponse" } } }, @@ -12587,23 +14776,24 @@ "description": "Validation Error" } }, - "summary": "List content embeddings", + "summary": "Send a test email from a verified domain to the account owner", "tags": [ - "contents" + "email-domains" ] } }, - "/contents/{source_connection_content_version}/upload": { + "/email-domains/{domain_id}/verify": { "post": { - "description": "Upload a new file and replace the content backing an existing `SourceConnectionContentVersion`.\n\nThis behaves like a source file upload, but it targets an existing content version ID. This is useful when you want to correct or update an uploaded document while keeping references stable.\n\n**Maximum file size:** 209715200 bytes.\n\n**Supported MIME types:**\n- `application/epub+zip`\n- `application/json`\n- `application/pdf`\n- `application/vnd.ms-excel`\n- `application/vnd.ms-outlook`\n- `application/vnd.ms-powerpoint`\n- `application/vnd.openxmlformats-officedocument.presentationml.presentation`\n- `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`\n- `application/vnd.openxmlformats-officedocument.wordprocessingml.document`\n- `application/xml`\n- `application/zip`\n- `audio/flac`\n- `audio/mp4`\n- `audio/mpeg`\n- `audio/ogg`\n- `audio/wav`\n- `image/bmp`\n- `image/gif`\n- `image/jpeg`\n- `image/png`\n- `image/svg+xml`\n- `image/tiff`\n- `image/webp`\n- `text/csv`\n- `text/html`\n- `text/markdown`\n- `text/plain`\n- `text/x-markdown`\n- `text/xml`\n- `video/mp4`\n- `video/quicktime`\n- `video/x-msvideo`\n\nNotes:\n- If the uploaded file's content type is `application/octet-stream`, the server attempts to infer the type from the file extension.\n- Use `metadata` to attach an arbitrary JSON object of metadata (for example `metadata={\"category\":\"docs\"}`).\n- `title` is a convenience field and is merged into the metadata as `metadata.title` (it does not override an existing `metadata.title`).\n- For backwards compatibility, you can also pass form fields named `metadata_` (for example `metadata_author=...`). These override keys from `metadata`.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. You can only replace content belonging to your account.", - "operationId": "upload_file_to_content_api_contents__source_connection_content_version__upload_post", + "description": "Re-poll SES + DNS for this domain right now instead of waiting for the background verification sweep, and return its updated status + DNS-record check results. Useful right after publishing the required records. Owner/admin only.\n\nAuth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an account-only key is refused with 403); the domain is scoped to the key's account.", + "operationId": "verify_email_domain_api_api_email_domains__domain_id__verify_post", "parameters": [ { "in": "path", - "name": "source_connection_content_version", + "name": "domain_id", "required": true, "schema": { - "title": "Source Connection Content Version", + "format": "uuid", + "title": "Domain Id", "type": "string" } }, @@ -12611,22 +14801,12 @@ "$ref": "#/components/parameters/X-Account-Id" } ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_upload_file_to_content_api_contents__source_connection_content_version__upload_post" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/routers__api__contents__FileUploadResponse" + "$ref": "#/components/schemas/EmailDomainResponse" } } }, @@ -12643,9 +14823,9 @@ "description": "Validation Error" } }, - "summary": "Replace a content version with a new upload", + "summary": "Run a verification check immediately ('Check now')", "tags": [ - "contents" + "email-domains" ] } }, @@ -13993,7 +16173,7 @@ }, "/models": { "get": { - "description": "List all enabled LLM models with full details.\n\nReturns models grouped by provider, including capabilities, credit pricing, tool support, variant tiers, and lifecycle status.\n\nOptional query parameters:\n- `provider`: filter by provider (e.g. 'anthropic', 'openai')\n- `supports_tool_use`: filter to models with tool calling support\n- `supports_thinking`: filter to models with extended thinking support\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "description": "List all enabled LLM models with full details.\n\nReturns models grouped by provider, including capabilities, credit pricing, tool support, variant tiers, and lifecycle status.\n\nOptional query parameters:\n- `provider`: filter by provider (e.g. 'anthropic', 'openai')\n- `supports_tool_use`: filter to models with tool calling support\n- `supports_thinking`: filter to models with extended thinking support\n- `supports_input_media`: filter to models accepting an input modality (`image`/`audio`/`video`/`pdf` or a full MIME)\n- `supports_output_media`: filter to models producing an output modality (`image`/`audio`/`video`) \u2014 e.g. to find generation models\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", "operationId": "list_models_api_models_get", "parameters": [ { @@ -14050,6 +16230,42 @@ "title": "Supports Thinking" } }, + { + "description": "Filter to models that accept this input modality \u2014 a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME.", + "in": "query", + "name": "supports_input_media", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter to models that accept this input modality \u2014 a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME.", + "title": "Supports Input Media" + } + }, + { + "description": "Filter to models that produce this output modality \u2014 a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find image/audio/video generation models.", + "in": "query", + "name": "supports_output_media", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter to models that produce this output modality \u2014 a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find image/audio/video generation models.", + "title": "Supports Output Media" + } + }, { "$ref": "#/components/parameters/X-Account-Id" } @@ -14271,6 +16487,35 @@ ] } }, + "/models/generation-tiers": { + "get": { + "description": "List the media-generation quality tiers and the model + cost each resolves to.\n\nOn a prompt_call's `media_generation` tool \u2014 and the dedicated generate_* steps via tier routing \u2014 the author/LLM chooses a *tier* (fast/balanced/thorough), never a model. This is the surface that maps each `(modality, tier)` to its concrete generator, raw `credits_per_unit`, `unit_label`, and a human-readable scaled `price_label`. Global routing/pricing (the same for every account); read-only. REST parity with the `list_generation_tiers` MCP tool.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token.", + "operationId": "get_generation_tiers_api_models_generation_tiers_get", + "parameters": [ + { + "$ref": "#/components/parameters/X-Account-Id" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "title": "Response Get Generation Tiers Api Models Generation Tiers Get", + "type": "object" + } + } + }, + "description": "Successful Response" + } + }, + "summary": "Get Generation Tiers", + "tags": [ + "models" + ] + } + }, "/models/playground/experiments": { "get": { "description": "List model playground experiments for the account.\n\nReturns a paginated, time-filtered list of experiments ordered by creation date descending.\n\nAuth & scoping:\n- Requires `X-API-Key` header or OAuth Bearer token. Experiments are scoped to the caller's account.", @@ -14758,7 +17003,7 @@ }, "/search": { "get": { - "description": "Search across all resource types in your account. Accepts a free-text keyword query or a UUID. UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, governance_policy.", + "description": "Search across all resource types in your account. Accepts a free-text keyword query or a UUID. UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, governance_policy, mcp_client.", "operationId": "search_api_search_get", "parameters": [ { diff --git a/pyproject.toml b/pyproject.toml index ef9d2f0..2b8abce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ extend-exclude = ''' \.git | \.mypy_cache | \.venv + | \.claude )/ ''' @@ -67,6 +68,7 @@ plugins = ["pydantic.mypy"] exclude = [ "tests/conftest.py", "seclai/_generated/", + ".claude/", ] [[tool.mypy.overrides]] @@ -94,6 +96,7 @@ package-mode = true version = "1.0" include = [ "README.md", + "CHANGELOG.md", "LICENSE", "seclai/py.typed", ] @@ -136,6 +139,9 @@ filterwarnings = [ target-version = "py313" line-length = 88 exclude = [ + # Vendored from seclai/sdk-tools via git subtree — formatting it here would + # create drift from the canonical copy on every lint run. + ".claude", ] [tool.ruff.lint] diff --git a/seclai/_generated/api/agents/block_email_sender_api_api_agents_blocked_email_senders_post.py b/seclai/_generated/api/agents/block_email_sender_api_api_agents_blocked_email_senders_post.py new file mode 100644 index 0000000..7c7ee18 --- /dev/null +++ b/seclai/_generated/api/agents/block_email_sender_api_api_agents_blocked_email_senders_post.py @@ -0,0 +1,210 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.block_email_sender_request import BlockEmailSenderRequest +from ...models.blocked_email_sender_response import BlockedEmailSenderResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + body: BlockEmailSenderRequest, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/agents/blocked-email-senders", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> BlockedEmailSenderResponse | HTTPValidationError | None: + if response.status_code == 201: + response_201 = BlockedEmailSenderResponse.from_dict(response.json()) + + return response_201 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[BlockedEmailSenderResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: BlockEmailSenderRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderResponse | HTTPValidationError]: + """Block an inbound email sender or domain + + Add a sender address or a whole domain to the account blocklist (idempotent; `match_type` is + `address` (default) or `domain`). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (BlockEmailSenderRequest): Add one sender/domain to the account blocklist (shared + REST request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + body: BlockEmailSenderRequest, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderResponse | HTTPValidationError | None: + """Block an inbound email sender or domain + + Add a sender address or a whole domain to the account blocklist (idempotent; `match_type` is + `address` (default) or `domain`). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (BlockEmailSenderRequest): Add one sender/domain to the account blocklist (shared + REST request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderResponse | HTTPValidationError + """ + + return sync_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: BlockEmailSenderRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderResponse | HTTPValidationError]: + """Block an inbound email sender or domain + + Add a sender address or a whole domain to the account blocklist (idempotent; `match_type` is + `address` (default) or `domain`). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (BlockEmailSenderRequest): Add one sender/domain to the account blocklist (shared + REST request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: BlockEmailSenderRequest, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderResponse | HTTPValidationError | None: + """Block an inbound email sender or domain + + Add a sender address or a whole domain to the account blocklist (idempotent; `match_type` is + `address` (default) or `domain`). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (BlockEmailSenderRequest): Add one sender/domain to the account blocklist (shared + REST request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/cancel_queued_email_runs_api_api_agents_inbound_email_status_cancel_queued_post.py b/seclai/_generated/api/agents/cancel_queued_email_runs_api_api_agents_inbound_email_status_cancel_queued_post.py new file mode 100644 index 0000000..a8c7e23 --- /dev/null +++ b/seclai/_generated/api/agents/cancel_queued_email_runs_api_api_agents_inbound_email_status_cancel_queued_post.py @@ -0,0 +1,182 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.cancel_queued_runs_response import CancelQueuedRunsResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/agents/inbound-email-status/cancel-queued", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> CancelQueuedRunsResponse | None: + if response.status_code == 200: + response_200 = CancelQueuedRunsResponse.from_dict(response.json()) + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[CancelQueuedRunsResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[CancelQueuedRunsResponse]: + """Cancel all queued inbound-email runs + + Fail all of the account's QUEUED (over-quota parked) inbound-email runs at once. A queued run + consumed no quota or credits at queue time, so this merely fails them. Returns the count cancelled. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[CancelQueuedRunsResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> CancelQueuedRunsResponse | None: + """Cancel all queued inbound-email runs + + Fail all of the account's QUEUED (over-quota parked) inbound-email runs at once. A queued run + consumed no quota or credits at queue time, so this merely fails them. Returns the count cancelled. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + CancelQueuedRunsResponse + """ + + return sync_detailed( + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[CancelQueuedRunsResponse]: + """Cancel all queued inbound-email runs + + Fail all of the account's QUEUED (over-quota parked) inbound-email runs at once. A queued run + consumed no quota or credits at queue time, so this merely fails them. Returns the count cancelled. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[CancelQueuedRunsResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> CancelQueuedRunsResponse | None: + """Cancel all queued inbound-email runs + + Fail all of the account's QUEUED (over-quota parked) inbound-email runs at once. A queued run + consumed no quota or credits at queue time, so this merely fails them. Returns the count cancelled. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + CancelQueuedRunsResponse + """ + + return ( + await asyncio_detailed( + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/create_agent_api_agents_post.py b/seclai/_generated/api/agents/create_agent_api_agents_post.py index 8d83197..80e7f29 100644 --- a/seclai/_generated/api/agents/create_agent_api_agents_post.py +++ b/seclai/_generated/api/agents/create_agent_api_agents_post.py @@ -85,6 +85,8 @@ def sync_detailed( - `template_input`: triggered via API with a predefined template - `schedule`: triggered on a schedule - `new_content`: triggered when new content arrives + - `email_received`: a virtual email inbox; runs when mail arrives at the agent's address. Configure + the alias/allowlist with `PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config`. Templates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline` @@ -139,6 +141,8 @@ def sync( - `template_input`: triggered via API with a predefined template - `schedule`: triggered on a schedule - `new_content`: triggered when new content arrives + - `email_received`: a virtual email inbox; runs when mail arrives at the agent's address. Configure + the alias/allowlist with `PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config`. Templates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline` @@ -188,6 +192,8 @@ async def asyncio_detailed( - `template_input`: triggered via API with a predefined template - `schedule`: triggered on a schedule - `new_content`: triggered when new content arrives + - `email_received`: a virtual email inbox; runs when mail arrives at the agent's address. Configure + the alias/allowlist with `PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config`. Templates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline` @@ -240,6 +246,8 @@ async def asyncio( - `template_input`: triggered via API with a predefined template - `schedule`: triggered on a schedule - `new_content`: triggered when new content arrives + - `email_received`: a virtual email inbox; runs when mail arrives at the agent's address. Configure + the alias/allowlist with `PUT /api/agents/{agent_id}/triggers/{trigger_id}/email-config`. Templates: `blank`, `retrieval_example`, `simple_qa`, `summarizer`, `json_extractor`, `content_change_notifier`, `scheduled_report`, `webhook_pipeline` diff --git a/seclai/_generated/api/agents/disable_agent_api_api_agents_agent_id_disable_post.py b/seclai/_generated/api/agents/disable_agent_api_api_agents_agent_id_disable_post.py new file mode 100644 index 0000000..15ea430 --- /dev/null +++ b/seclai/_generated/api/agents/disable_agent_api_api_agents_agent_id_disable_post.py @@ -0,0 +1,216 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.agent_summary_response import AgentSummaryResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + agent_id: str, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/agents/{agent_id}/disable".format( + agent_id=quote(str(agent_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> AgentSummaryResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = AgentSummaryResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[AgentSummaryResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentSummaryResponse | HTTPValidationError]: + """Pause (disable) an agent + + Disable an agent so it stops firing from every trigger path (API runs return 409, inbound email is + turned away, scheduled/content triggers are skipped). + + Returns **409** with the blocking callers when other live agents still call this one via a + `call_agent` step — disable those first. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> AgentSummaryResponse | HTTPValidationError | None: + """Pause (disable) an agent + + Disable an agent so it stops firing from every trigger path (API runs return 409, inbound email is + turned away, scheduled/content triggers are skipped). + + Returns **409** with the blocking callers when other live agents still call this one via a + `call_agent` step — disable those first. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentSummaryResponse | HTTPValidationError + """ + + return sync_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentSummaryResponse | HTTPValidationError]: + """Pause (disable) an agent + + Disable an agent so it stops firing from every trigger path (API runs return 409, inbound email is + turned away, scheduled/content triggers are skipped). + + Returns **409** with the blocking callers when other live agents still call this one via a + `call_agent` step — disable those first. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> AgentSummaryResponse | HTTPValidationError | None: + """Pause (disable) an agent + + Disable an agent so it stops firing from every trigger path (API runs return 409, inbound email is + turned away, scheduled/content triggers are skipped). + + Returns **409** with the blocking callers when other live agents still call this one via a + `call_agent` step — disable those first. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentSummaryResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/enable_agent_api_api_agents_agent_id_enable_post.py b/seclai/_generated/api/agents/enable_agent_api_api_agents_agent_id_enable_post.py new file mode 100644 index 0000000..4bcb509 --- /dev/null +++ b/seclai/_generated/api/agents/enable_agent_api_api_agents_agent_id_enable_post.py @@ -0,0 +1,204 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.agent_summary_response import AgentSummaryResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + agent_id: str, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/agents/{agent_id}/enable".format( + agent_id=quote(str(agent_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> AgentSummaryResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = AgentSummaryResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[AgentSummaryResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentSummaryResponse | HTTPValidationError]: + """Resume (enable) a paused agent + + Re-enable a paused agent (clears the disable state, whether it was paused manually or auto-paused by + the inbound-email overload safeguard). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> AgentSummaryResponse | HTTPValidationError | None: + """Resume (enable) a paused agent + + Re-enable a paused agent (clears the disable state, whether it was paused manually or auto-paused by + the inbound-email overload safeguard). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentSummaryResponse | HTTPValidationError + """ + + return sync_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentSummaryResponse | HTTPValidationError]: + """Resume (enable) a paused agent + + Re-enable a paused agent (clears the disable state, whether it was paused manually or auto-paused by + the inbound-email overload safeguard). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> AgentSummaryResponse | HTTPValidationError | None: + """Resume (enable) a paused agent + + Re-enable a paused agent (clears the disable state, whether it was paused manually or auto-paused by + the inbound-email overload safeguard). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token bound to a user; the agent must + belong to the key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentSummaryResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/get_agent_callers_api_api_agents_agent_id_callers_get.py b/seclai/_generated/api/agents/get_agent_callers_api_api_agents_agent_id_callers_get.py new file mode 100644 index 0000000..b61cd0b --- /dev/null +++ b/seclai/_generated/api/agents/get_agent_callers_api_api_agents_agent_id_callers_get.py @@ -0,0 +1,209 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.agent_caller_api_response import AgentCallerApiResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + agent_id: str, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/agents/{agent_id}/callers".format( + agent_id=quote(str(agent_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | list[AgentCallerApiResponse] | None: + if response.status_code == 200: + response_200 = [] + _response_200 = response.json() + for response_200_item_data in _response_200: + response_200_item = AgentCallerApiResponse.from_dict(response_200_item_data) + + response_200.append(response_200_item) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | list[AgentCallerApiResponse]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | list[AgentCallerApiResponse]]: + """List agents that call this agent + + List the live agents that call this agent via a `call_agent` step. They must be disabled before this + agent can be paused. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the agent must belong to the + key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | list[AgentCallerApiResponse]] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | list[AgentCallerApiResponse] | None: + """List agents that call this agent + + List the live agents that call this agent via a `call_agent` step. They must be disabled before this + agent can be paused. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the agent must belong to the + key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | list[AgentCallerApiResponse] + """ + + return sync_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | list[AgentCallerApiResponse]]: + """List agents that call this agent + + List the live agents that call this agent via a `call_agent` step. They must be disabled before this + agent can be paused. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the agent must belong to the + key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | list[AgentCallerApiResponse]] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + agent_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | list[AgentCallerApiResponse] | None: + """List agents that call this agent + + List the live agents that call this agent via a `call_agent` step. They must be disabled before this + agent can be paused. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the agent must belong to the + key's account. + + Args: + agent_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | list[AgentCallerApiResponse] + """ + + return ( + await asyncio_detailed( + agent_id=agent_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/get_inbound_email_status_api_api_agents_inbound_email_status_get.py b/seclai/_generated/api/agents/get_inbound_email_status_api_api_agents_inbound_email_status_get.py new file mode 100644 index 0000000..8513514 --- /dev/null +++ b/seclai/_generated/api/agents/get_inbound_email_status_api_api_agents_inbound_email_status_get.py @@ -0,0 +1,186 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.inbound_email_status_response import InboundEmailStatusResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/agents/inbound-email-status", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> InboundEmailStatusResponse | None: + if response.status_code == 200: + response_200 = InboundEmailStatusResponse.from_dict(response.json()) + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[InboundEmailStatusResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[InboundEmailStatusResponse]: + """Account inbound-email overload status + + Whether the account-wide overload circuit breaker has currently paused new inbound email (all + inbound turned away until the queued backlog drains), plus the size of the QUEUED (over-quota + parked) run backlog. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[InboundEmailStatusResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> InboundEmailStatusResponse | None: + """Account inbound-email overload status + + Whether the account-wide overload circuit breaker has currently paused new inbound email (all + inbound turned away until the queued backlog drains), plus the size of the QUEUED (over-quota + parked) run backlog. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + InboundEmailStatusResponse + """ + + return sync_detailed( + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[InboundEmailStatusResponse]: + """Account inbound-email overload status + + Whether the account-wide overload circuit breaker has currently paused new inbound email (all + inbound turned away until the queued backlog drains), plus the size of the QUEUED (over-quota + parked) run backlog. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[InboundEmailStatusResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> InboundEmailStatusResponse | None: + """Account inbound-email overload status + + Whether the account-wide overload circuit breaker has currently paused new inbound email (all + inbound turned away until the queued backlog drains), plus the size of the QUEUED (over-quota + parked) run backlog. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + InboundEmailStatusResponse + """ + + return ( + await asyncio_detailed( + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/list_agent_email_optouts_api_api_agents_agent_email_optouts_get.py b/seclai/_generated/api/agents/list_agent_email_optouts_api_api_agents_agent_email_optouts_get.py new file mode 100644 index 0000000..337a1a5 --- /dev/null +++ b/seclai/_generated/api/agents/list_agent_email_optouts_api_api_agents_agent_email_optouts_get.py @@ -0,0 +1,247 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.agent_email_opt_out_list_response import AgentEmailOptOutListResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + params: dict[str, Any] = {} + + json_agent_id: None | str | Unset + if isinstance(agent_id, Unset): + json_agent_id = UNSET + else: + json_agent_id = agent_id + params["agent_id"] = json_agent_id + + params["limit"] = limit + + params["offset"] = offset + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/agents/agent-email-optouts", + "params": params, + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> AgentEmailOptOutListResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = AgentEmailOptOutListResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[AgentEmailOptOutListResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentEmailOptOutListResponse | HTTPValidationError]: + """List agent-email opt-outs + + List recipients who have opted out of this account's agent emails (filter to one agent via + `agent_id`; account-wide opt-outs always apply). Paginated via `limit`/`offset`; returns the page + plus the `total` count. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to one agent (account-wide opt-outs still apply) + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentEmailOptOutListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> AgentEmailOptOutListResponse | HTTPValidationError | None: + """List agent-email opt-outs + + List recipients who have opted out of this account's agent emails (filter to one agent via + `agent_id`; account-wide opt-outs always apply). Paginated via `limit`/`offset`; returns the page + plus the `total` count. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to one agent (account-wide opt-outs still apply) + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentEmailOptOutListResponse | HTTPValidationError + """ + + return sync_detailed( + client=client, + agent_id=agent_id, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> Response[AgentEmailOptOutListResponse | HTTPValidationError]: + """List agent-email opt-outs + + List recipients who have opted out of this account's agent emails (filter to one agent via + `agent_id`; account-wide opt-outs always apply). Paginated via `limit`/`offset`; returns the page + plus the `total` count. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to one agent (account-wide opt-outs still apply) + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentEmailOptOutListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> AgentEmailOptOutListResponse | HTTPValidationError | None: + """List agent-email opt-outs + + List recipients who have opted out of this account's agent emails (filter to one agent via + `agent_id`; account-wide opt-outs always apply). Paginated via `limit`/`offset`; returns the page + plus the `total` count. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to one agent (account-wide opt-outs still apply) + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentEmailOptOutListResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + agent_id=agent_id, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/list_blocked_email_senders_api_api_agents_blocked_email_senders_get.py b/seclai/_generated/api/agents/list_blocked_email_senders_api_api_agents_blocked_email_senders_get.py new file mode 100644 index 0000000..b92233f --- /dev/null +++ b/seclai/_generated/api/agents/list_blocked_email_senders_api_api_agents_blocked_email_senders_get.py @@ -0,0 +1,223 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.blocked_email_sender_list_response import BlockedEmailSenderListResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + params: dict[str, Any] = {} + + params["limit"] = limit + + params["offset"] = offset + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/agents/blocked-email-senders", + "params": params, + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = BlockedEmailSenderListResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + """List blocked inbound email senders + the auto-block mode + + List the account's blocked inbound email senders (newest first, paginated via `limit`/`offset`) plus + the governance `auto_block_mode`. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + """List blocked inbound email senders + the auto-block mode + + List the account's blocked inbound email senders (newest first, paginated via `limit`/`offset`) plus + the governance `auto_block_mode`. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderListResponse | HTTPValidationError + """ + + return sync_detailed( + client=client, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + """List blocked inbound email senders + the auto-block mode + + List the account's blocked inbound email senders (newest first, paginated via `limit`/`offset`) plus + the governance `auto_block_mode`. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + offset: int | Unset = 0, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + """List blocked inbound email senders + the auto-block mode + + List the account's blocked inbound email senders (newest first, paginated via `limit`/`offset`) plus + the governance `auto_block_mode`. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + limit (int | Unset): Default: 50. + offset (int | Unset): Default: 0. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderListResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + limit=limit, + offset=offset, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/list_inbound_email_rejections_api_api_agents_inbound_email_rejections_get.py b/seclai/_generated/api/agents/list_inbound_email_rejections_api_api_agents_inbound_email_rejections_get.py new file mode 100644 index 0000000..ce5f7a1 --- /dev/null +++ b/seclai/_generated/api/agents/list_inbound_email_rejections_api_api_agents_inbound_email_rejections_get.py @@ -0,0 +1,235 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...models.inbound_email_rejection_response import InboundEmailRejectionResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + params: dict[str, Any] = {} + + json_agent_id: None | str | Unset + if isinstance(agent_id, Unset): + json_agent_id = UNSET + else: + json_agent_id = agent_id + params["agent_id"] = json_agent_id + + params["limit"] = limit + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/agents/inbound-email-rejections", + "params": params, + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | list[InboundEmailRejectionResponse] | None: + if response.status_code == 200: + response_200 = [] + _response_200 = response.json() + for response_200_item_data in _response_200: + response_200_item = InboundEmailRejectionResponse.from_dict( + response_200_item_data + ) + + response_200.append(response_200_item) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | list[InboundEmailRejectionResponse]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | list[InboundEmailRejectionResponse]]: + """List discarded inbound emails + + List recent inbound emails that were quietly discarded before running an agent (unauthorized sender, + unknown alias, spam/virus, flood-shed) for this account's agents. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to a single agent's rejections + limit (int | Unset): Default: 50. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | list[InboundEmailRejectionResponse]] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + limit=limit, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | list[InboundEmailRejectionResponse] | None: + """List discarded inbound emails + + List recent inbound emails that were quietly discarded before running an agent (unauthorized sender, + unknown alias, spam/virus, flood-shed) for this account's agents. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to a single agent's rejections + limit (int | Unset): Default: 50. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | list[InboundEmailRejectionResponse] + """ + + return sync_detailed( + client=client, + agent_id=agent_id, + limit=limit, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | list[InboundEmailRejectionResponse]]: + """List discarded inbound emails + + List recent inbound emails that were quietly discarded before running an agent (unauthorized sender, + unknown alias, spam/virus, flood-shed) for this account's agents. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to a single agent's rejections + limit (int | Unset): Default: 50. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | list[InboundEmailRejectionResponse]] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + limit=limit, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + agent_id: None | str | Unset = UNSET, + limit: int | Unset = 50, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | list[InboundEmailRejectionResponse] | None: + """List discarded inbound emails + + List recent inbound emails that were quietly discarded before running an agent (unauthorized sender, + unknown alias, spam/virus, flood-shed) for this account's agents. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; always scoped to the key's + account. + + Args: + agent_id (None | str | Unset): Filter to a single agent's rejections + limit (int | Unset): Default: 50. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | list[InboundEmailRejectionResponse] + """ + + return ( + await asyncio_detailed( + client=client, + agent_id=agent_id, + limit=limit, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/remove_agent_email_optout_api_api_agents_agent_email_optouts_optout_id_delete.py b/seclai/_generated/api/agents/remove_agent_email_optout_api_api_agents_agent_email_optouts_optout_id_delete.py new file mode 100644 index 0000000..809f6d0 --- /dev/null +++ b/seclai/_generated/api/agents/remove_agent_email_optout_api_api_agents_agent_email_optouts_optout_id_delete.py @@ -0,0 +1,198 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + optout_id: str, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "delete", + "url": "/agents/agent-email-optouts/{optout_id}".format( + optout_id=quote(str(optout_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Any | HTTPValidationError | None: + if response.status_code == 204: + response_204 = cast(Any, None) + return response_204 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Any | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + optout_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any | HTTPValidationError]: + """Revoke an agent-email opt-out + + Revoke an opt-out (opt the recipient back in to agent emails). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the opt-out must belong to the + key's account. + + Args: + optout_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError] + """ + + kwargs = _get_kwargs( + optout_id=optout_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + optout_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Any | HTTPValidationError | None: + """Revoke an agent-email opt-out + + Revoke an opt-out (opt the recipient back in to agent emails). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the opt-out must belong to the + key's account. + + Args: + optout_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError + """ + + return sync_detailed( + optout_id=optout_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + optout_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any | HTTPValidationError]: + """Revoke an agent-email opt-out + + Revoke an opt-out (opt the recipient back in to agent emails). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the opt-out must belong to the + key's account. + + Args: + optout_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError] + """ + + kwargs = _get_kwargs( + optout_id=optout_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + optout_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Any | HTTPValidationError | None: + """Revoke an agent-email opt-out + + Revoke an opt-out (opt the recipient back in to agent emails). + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the opt-out must belong to the + key's account. + + Args: + optout_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError + """ + + return ( + await asyncio_detailed( + optout_id=optout_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/resume_inbound_email_api_api_agents_inbound_email_status_resume_post.py b/seclai/_generated/api/agents/resume_inbound_email_api_api_agents_inbound_email_status_resume_post.py new file mode 100644 index 0000000..7a7509c --- /dev/null +++ b/seclai/_generated/api/agents/resume_inbound_email_api_api_agents_inbound_email_status_resume_post.py @@ -0,0 +1,182 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.resume_inbound_response import ResumeInboundResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/agents/inbound-email-status/resume", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ResumeInboundResponse | None: + if response.status_code == 200: + response_200 = ResumeInboundResponse.from_dict(response.json()) + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ResumeInboundResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[ResumeInboundResponse]: + """Manually resume paused inbound email + + Manually lift the account-wide inbound pause. If the queued backlog is still above the ceiling the + breaker re-arms on the next evaluation — this is a one-shot override, not a permanent disable. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ResumeInboundResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> ResumeInboundResponse | None: + """Manually resume paused inbound email + + Manually lift the account-wide inbound pause. If the queued backlog is still above the ceiling the + breaker re-arms on the next evaluation — this is a one-shot override, not a permanent disable. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ResumeInboundResponse + """ + + return sync_detailed( + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[ResumeInboundResponse]: + """Manually resume paused inbound email + + Manually lift the account-wide inbound pause. If the queued backlog is still above the ceiling the + breaker re-arms on the next evaluation — this is a one-shot override, not a permanent disable. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ResumeInboundResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> ResumeInboundResponse | None: + """Manually resume paused inbound email + + Manually lift the account-wide inbound pause. If the queued backlog is still above the ceiling the + breaker re-arms on the next evaluation — this is a one-shot override, not a permanent disable. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ResumeInboundResponse + """ + + return ( + await asyncio_detailed( + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/set_auto_block_mode_api_api_agents_blocked_email_senders_mode_put.py b/seclai/_generated/api/agents/set_auto_block_mode_api_api_agents_blocked_email_senders_mode_put.py new file mode 100644 index 0000000..8f555a0 --- /dev/null +++ b/seclai/_generated/api/agents/set_auto_block_mode_api_api_agents_blocked_email_senders_mode_put.py @@ -0,0 +1,210 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.blocked_email_sender_list_response import BlockedEmailSenderListResponse +from ...models.http_validation_error import HTTPValidationError +from ...models.set_auto_block_mode_request import SetAutoBlockModeRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + body: SetAutoBlockModeRequest, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "put", + "url": "/agents/blocked-email-senders/mode", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = BlockedEmailSenderListResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: SetAutoBlockModeRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + """Set the governance auto-block mode + + Set whether a governance BLOCK on an authenticated inbound email sender auto-adds them to the + blocklist (`disabled`, `input`, or `input_and_output`); returns the updated list. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (SetAutoBlockModeRequest): Set the account's governance auto-block mode (shared REST + request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + body: SetAutoBlockModeRequest, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + """Set the governance auto-block mode + + Set whether a governance BLOCK on an authenticated inbound email sender auto-adds them to the + blocklist (`disabled`, `input`, or `input_and_output`); returns the updated list. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (SetAutoBlockModeRequest): Set the account's governance auto-block mode (shared REST + request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderListResponse | HTTPValidationError + """ + + return sync_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: SetAutoBlockModeRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[BlockedEmailSenderListResponse | HTTPValidationError]: + """Set the governance auto-block mode + + Set whether a governance BLOCK on an authenticated inbound email sender auto-adds them to the + blocklist (`disabled`, `input`, or `input_and_output`); returns the updated list. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (SetAutoBlockModeRequest): Set the account's governance auto-block mode (shared REST + request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[BlockedEmailSenderListResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: SetAutoBlockModeRequest, + x_account_id: UUID | Unset = UNSET, +) -> BlockedEmailSenderListResponse | HTTPValidationError | None: + """Set the governance auto-block mode + + Set whether a governance BLOCK on an authenticated inbound email sender auto-adds them to the + blocklist (`disabled`, `input`, or `input_and_output`); returns the updated list. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; scoped + to the key's account. + + Args: + x_account_id (UUID | Unset): + body (SetAutoBlockModeRequest): Set the account's governance auto-block mode (shared REST + request). + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + BlockedEmailSenderListResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/set_email_trigger_config_api_api_agents_agent_id_triggers_trigger_id_email_config_put.py b/seclai/_generated/api/agents/set_email_trigger_config_api_api_agents_agent_id_triggers_trigger_id_email_config_put.py new file mode 100644 index 0000000..18baa9a --- /dev/null +++ b/seclai/_generated/api/agents/set_email_trigger_config_api_api_agents_agent_id_triggers_trigger_id_email_config_put.py @@ -0,0 +1,256 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.email_trigger_config_response import EmailTriggerConfigResponse +from ...models.http_validation_error import HTTPValidationError +from ...models.set_email_trigger_config_request import SetEmailTriggerConfigRequest +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + agent_id: str, + trigger_id: str, + *, + body: SetEmailTriggerConfigRequest, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "put", + "url": "/agents/{agent_id}/triggers/{trigger_id}/email-config".format( + agent_id=quote(str(agent_id), safe=""), + trigger_id=quote(str(trigger_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> EmailTriggerConfigResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = EmailTriggerConfigResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[EmailTriggerConfigResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + agent_id: str, + trigger_id: str, + *, + client: AuthenticatedClient | Client, + body: SetEmailTriggerConfigRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailTriggerConfigResponse | HTTPValidationError]: + """Configure an EMAIL_RECEIVED trigger + + Set the custom alias, sender allowlist, and inbound-handling flags (`ignore_auto_generated`, + `require_sender_auth`, `queue_on_quota`) on an agent's EMAIL_RECEIVED trigger, and return its + computed email address(es). Omitted fields are left unchanged. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the trigger must belong to an + agent in the key's account. + + Args: + agent_id (str): + trigger_id (str): + x_account_id (UUID | Unset): + body (SetEmailTriggerConfigRequest): Alias and/or sender allowlist for an EMAIL_RECEIVED + trigger. + + A field omitted is left unchanged; passing ``null`` (or ``""`` for + ``alias``) clears it. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailTriggerConfigResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + trigger_id=trigger_id, + body=body, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + agent_id: str, + trigger_id: str, + *, + client: AuthenticatedClient | Client, + body: SetEmailTriggerConfigRequest, + x_account_id: UUID | Unset = UNSET, +) -> EmailTriggerConfigResponse | HTTPValidationError | None: + """Configure an EMAIL_RECEIVED trigger + + Set the custom alias, sender allowlist, and inbound-handling flags (`ignore_auto_generated`, + `require_sender_auth`, `queue_on_quota`) on an agent's EMAIL_RECEIVED trigger, and return its + computed email address(es). Omitted fields are left unchanged. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the trigger must belong to an + agent in the key's account. + + Args: + agent_id (str): + trigger_id (str): + x_account_id (UUID | Unset): + body (SetEmailTriggerConfigRequest): Alias and/or sender allowlist for an EMAIL_RECEIVED + trigger. + + A field omitted is left unchanged; passing ``null`` (or ``""`` for + ``alias``) clears it. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailTriggerConfigResponse | HTTPValidationError + """ + + return sync_detailed( + agent_id=agent_id, + trigger_id=trigger_id, + client=client, + body=body, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + agent_id: str, + trigger_id: str, + *, + client: AuthenticatedClient | Client, + body: SetEmailTriggerConfigRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailTriggerConfigResponse | HTTPValidationError]: + """Configure an EMAIL_RECEIVED trigger + + Set the custom alias, sender allowlist, and inbound-handling flags (`ignore_auto_generated`, + `require_sender_auth`, `queue_on_quota`) on an agent's EMAIL_RECEIVED trigger, and return its + computed email address(es). Omitted fields are left unchanged. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the trigger must belong to an + agent in the key's account. + + Args: + agent_id (str): + trigger_id (str): + x_account_id (UUID | Unset): + body (SetEmailTriggerConfigRequest): Alias and/or sender allowlist for an EMAIL_RECEIVED + trigger. + + A field omitted is left unchanged; passing ``null`` (or ``""`` for + ``alias``) clears it. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailTriggerConfigResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + agent_id=agent_id, + trigger_id=trigger_id, + body=body, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + agent_id: str, + trigger_id: str, + *, + client: AuthenticatedClient | Client, + body: SetEmailTriggerConfigRequest, + x_account_id: UUID | Unset = UNSET, +) -> EmailTriggerConfigResponse | HTTPValidationError | None: + """Configure an EMAIL_RECEIVED trigger + + Set the custom alias, sender allowlist, and inbound-handling flags (`ignore_auto_generated`, + `require_sender_auth`, `queue_on_quota`) on an agent's EMAIL_RECEIVED trigger, and return its + computed email address(es). Omitted fields are left unchanged. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token; the trigger must belong to an + agent in the key's account. + + Args: + agent_id (str): + trigger_id (str): + x_account_id (UUID | Unset): + body (SetEmailTriggerConfigRequest): Alias and/or sender allowlist for an EMAIL_RECEIVED + trigger. + + A field omitted is left unchanged; passing ``null`` (or ``""`` for + ``alias``) clears it. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailTriggerConfigResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + agent_id=agent_id, + trigger_id=trigger_id, + client=client, + body=body, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/agents/unblock_email_sender_api_api_agents_blocked_email_senders_blocked_id_delete.py b/seclai/_generated/api/agents/unblock_email_sender_api_api_agents_blocked_email_senders_blocked_id_delete.py new file mode 100644 index 0000000..d14daf0 --- /dev/null +++ b/seclai/_generated/api/agents/unblock_email_sender_api_api_agents_blocked_email_senders_blocked_id_delete.py @@ -0,0 +1,198 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + blocked_id: str, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "delete", + "url": "/agents/blocked-email-senders/{blocked_id}".format( + blocked_id=quote(str(blocked_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Any | HTTPValidationError | None: + if response.status_code == 204: + response_204 = cast(Any, None) + return response_204 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Any | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + blocked_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any | HTTPValidationError]: + """Unblock an inbound email sender + + Remove a blocked sender by id. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; the + blocked sender must belong to the key's account. + + Args: + blocked_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError] + """ + + kwargs = _get_kwargs( + blocked_id=blocked_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + blocked_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Any | HTTPValidationError | None: + """Unblock an inbound email sender + + Remove a blocked sender by id. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; the + blocked sender must belong to the key's account. + + Args: + blocked_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError + """ + + return sync_detailed( + blocked_id=blocked_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + blocked_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any | HTTPValidationError]: + """Unblock an inbound email sender + + Remove a blocked sender by id. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; the + blocked sender must belong to the key's account. + + Args: + blocked_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any | HTTPValidationError] + """ + + kwargs = _get_kwargs( + blocked_id=blocked_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + blocked_id: str, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Any | HTTPValidationError | None: + """Unblock an inbound email sender + + Remove a blocked sender by id. + + Auth & scoping: requires `X-API-Key` header or OAuth Bearer token for an account owner/admin; the + blocked sender must belong to the key's account. + + Args: + blocked_id (str): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | HTTPValidationError + """ + + return ( + await asyncio_detailed( + blocked_id=blocked_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/__init__.py b/seclai/_generated/api/email_domains/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/seclai/_generated/api/email_domains/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/seclai/_generated/api/email_domains/add_email_domain_api_api_email_domains_post.py b/seclai/_generated/api/email_domains/add_email_domain_api_api_email_domains_post.py new file mode 100644 index 0000000..ba77856 --- /dev/null +++ b/seclai/_generated/api/email_domains/add_email_domain_api_api_email_domains_post.py @@ -0,0 +1,214 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.add_email_domain_request import AddEmailDomainRequest +from ...models.email_domain_response import EmailDomainResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + body: AddEmailDomainRequest, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/email-domains", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> EmailDomainResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = EmailDomainResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[EmailDomainResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: AddEmailDomainRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Add + provision an email domain + + Add a new vanity subdomain (`kind=vanity`, `value=`) or custom domain (`kind=custom`, + `value=agent.mycompany.com`; optional `delegated=true` to let Seclai manage a dedicated Route53 + zone). Stands up the SES identity + DNS and returns the records the customer must publish. + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + body (AddEmailDomainRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + body: AddEmailDomainRequest, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Add + provision an email domain + + Add a new vanity subdomain (`kind=vanity`, `value=`) or custom domain (`kind=custom`, + `value=agent.mycompany.com`; optional `delegated=true` to let Seclai manage a dedicated Route53 + zone). Stands up the SES identity + DNS and returns the records the customer must publish. + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + body (AddEmailDomainRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return sync_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: AddEmailDomainRequest, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Add + provision an email domain + + Add a new vanity subdomain (`kind=vanity`, `value=`) or custom domain (`kind=custom`, + `value=agent.mycompany.com`; optional `delegated=true` to let Seclai manage a dedicated Route53 + zone). Stands up the SES identity + DNS and returns the records the customer must publish. + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + body (AddEmailDomainRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + body=body, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: AddEmailDomainRequest, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Add + provision an email domain + + Add a new vanity subdomain (`kind=vanity`, `value=`) or custom domain (`kind=custom`, + `value=agent.mycompany.com`; optional `delegated=true` to let Seclai manage a dedicated Route53 + zone). Stands up the SES identity + DNS and returns the records the customer must publish. + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + body (AddEmailDomainRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + body=body, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/get_dmarc_summary_api_api_email_domains_domain_id_dmarc_get.py b/seclai/_generated/api/email_domains/get_dmarc_summary_api_api_email_domains_domain_id_dmarc_get.py new file mode 100644 index 0000000..dfe718d --- /dev/null +++ b/seclai/_generated/api/email_domains/get_dmarc_summary_api_api_email_domains_domain_id_dmarc_get.py @@ -0,0 +1,247 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.dmarc_summary_response import DmarcSummaryResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: UUID, + *, + days: int | Unset = 30, + top_sources: int | Unset = 10, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + params: dict[str, Any] = {} + + params["days"] = days + + params["top_sources"] = top_sources + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/email-domains/{domain_id}/dmarc".format( + domain_id=quote(str(domain_id), safe=""), + ), + "params": params, + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> DmarcSummaryResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = DmarcSummaryResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[DmarcSummaryResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + days: int | Unset = 30, + top_sources: int | Unset = 10, + x_account_id: UUID | Unset = UNSET, +) -> Response[DmarcSummaryResponse | HTTPValidationError]: + """DMARC aggregate-report summary for a domain + + Pass rate, disposition breakdown (`none`/`quarantine`/`reject`), and top failing source IPs from the + DMARC `rua` aggregate reports over the last `days` (clamped by the service). Populated for domains + whose DNS zone Seclai controls (vanity + delegated custom); a self-managed custom domain keeps its + own DMARC reporting and returns an all-zero summary. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + days (int | Unset): Default: 30. + top_sources (int | Unset): Default: 10. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DmarcSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + days=days, + top_sources=top_sources, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + days: int | Unset = 30, + top_sources: int | Unset = 10, + x_account_id: UUID | Unset = UNSET, +) -> DmarcSummaryResponse | HTTPValidationError | None: + """DMARC aggregate-report summary for a domain + + Pass rate, disposition breakdown (`none`/`quarantine`/`reject`), and top failing source IPs from the + DMARC `rua` aggregate reports over the last `days` (clamped by the service). Populated for domains + whose DNS zone Seclai controls (vanity + delegated custom); a self-managed custom domain keeps its + own DMARC reporting and returns an all-zero summary. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + days (int | Unset): Default: 30. + top_sources (int | Unset): Default: 10. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DmarcSummaryResponse | HTTPValidationError + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + days=days, + top_sources=top_sources, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + days: int | Unset = 30, + top_sources: int | Unset = 10, + x_account_id: UUID | Unset = UNSET, +) -> Response[DmarcSummaryResponse | HTTPValidationError]: + """DMARC aggregate-report summary for a domain + + Pass rate, disposition breakdown (`none`/`quarantine`/`reject`), and top failing source IPs from the + DMARC `rua` aggregate reports over the last `days` (clamped by the service). Populated for domains + whose DNS zone Seclai controls (vanity + delegated custom); a self-managed custom domain keeps its + own DMARC reporting and returns an all-zero summary. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + days (int | Unset): Default: 30. + top_sources (int | Unset): Default: 10. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DmarcSummaryResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + days=days, + top_sources=top_sources, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + days: int | Unset = 30, + top_sources: int | Unset = 10, + x_account_id: UUID | Unset = UNSET, +) -> DmarcSummaryResponse | HTTPValidationError | None: + """DMARC aggregate-report summary for a domain + + Pass rate, disposition breakdown (`none`/`quarantine`/`reject`), and top failing source IPs from the + DMARC `rua` aggregate reports over the last `days` (clamped by the service). Populated for domains + whose DNS zone Seclai controls (vanity + delegated custom); a self-managed custom domain keeps its + own DMARC reporting and returns an all-zero summary. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + days (int | Unset): Default: 30. + top_sources (int | Unset): Default: 10. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DmarcSummaryResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + days=days, + top_sources=top_sources, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/list_email_domains_api_api_email_domains_get.py b/seclai/_generated/api/email_domains/list_email_domains_api_api_email_domains_get.py new file mode 100644 index 0000000..e2f839e --- /dev/null +++ b/seclai/_generated/api/email_domains/list_email_domains_api_api_email_domains_get.py @@ -0,0 +1,190 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.email_domains_list_response import EmailDomainsListResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/email-domains", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> EmailDomainsListResponse | None: + if response.status_code == 200: + response_200 = EmailDomainsListResponse.from_dict(response.json()) + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[EmailDomainsListResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainsListResponse]: + """List the account's email domains + plan capabilities + + List the account's vanity (`.seclai.com`) and custom (`agent.mycompany.com`) agent-email + domains with their verification status, the DNS records the customer must publish, and the plan + capabilities (`can_add_vanity`/`can_add_custom`) plus whether one of each kind already exists + (`has_vanity`/`has_custom` — the per-kind limit is 1). + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainsListResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainsListResponse | None: + """List the account's email domains + plan capabilities + + List the account's vanity (`.seclai.com`) and custom (`agent.mycompany.com`) agent-email + domains with their verification status, the DNS records the customer must publish, and the plan + capabilities (`can_add_vanity`/`can_add_custom`) plus whether one of each kind already exists + (`has_vanity`/`has_custom` — the per-kind limit is 1). + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainsListResponse + """ + + return sync_detailed( + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainsListResponse]: + """List the account's email domains + plan capabilities + + List the account's vanity (`.seclai.com`) and custom (`agent.mycompany.com`) agent-email + domains with their verification status, the DNS records the customer must publish, and the plan + capabilities (`can_add_vanity`/`can_add_custom`) plus whether one of each kind already exists + (`has_vanity`/`has_custom` — the per-kind limit is 1). + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainsListResponse] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainsListResponse | None: + """List the account's email domains + plan capabilities + + List the account's vanity (`.seclai.com`) and custom (`agent.mycompany.com`) agent-email + domains with their verification status, the DNS records the customer must publish, and the plan + capabilities (`can_add_vanity`/`can_add_custom`) plus whether one of each kind already exists + (`has_vanity`/`has_custom` — the per-kind limit is 1). + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainsListResponse + """ + + return ( + await asyncio_detailed( + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/remove_email_domain_api_api_email_domains_domain_id_delete.py b/seclai/_generated/api/email_domains/remove_email_domain_api_api_email_domains_domain_id_delete.py new file mode 100644 index 0000000..094115f --- /dev/null +++ b/seclai/_generated/api/email_domains/remove_email_domain_api_api_email_domains_domain_id_delete.py @@ -0,0 +1,212 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...models.remove_email_domain_response import RemoveEmailDomainResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: UUID, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "delete", + "url": "/email-domains/{domain_id}".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | RemoveEmailDomainResponse | None: + if response.status_code == 200: + response_200 = RemoveEmailDomainResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | RemoveEmailDomainResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | RemoveEmailDomainResponse]: + """Remove an email domain + + Remove a domain and tear down its SES identity + DNS / receipt-rule recipient. Returns a + `cleanup_note` when the removed domain was Seclai-managed (delegated), reminding the caller to + delete the registrar NS delegation record (dangling-delegation / subdomain-takeover guard). + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | RemoveEmailDomainResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | RemoveEmailDomainResponse | None: + """Remove an email domain + + Remove a domain and tear down its SES identity + DNS / receipt-rule recipient. Returns a + `cleanup_note` when the removed domain was Seclai-managed (delegated), reminding the caller to + delete the registrar NS delegation record (dangling-delegation / subdomain-takeover guard). + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | RemoveEmailDomainResponse + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | RemoveEmailDomainResponse]: + """Remove an email domain + + Remove a domain and tear down its SES identity + DNS / receipt-rule recipient. Returns a + `cleanup_note` when the removed domain was Seclai-managed (delegated), reminding the caller to + delete the registrar NS delegation record (dangling-delegation / subdomain-takeover guard). + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | RemoveEmailDomainResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | RemoveEmailDomainResponse | None: + """Remove an email domain + + Remove a domain and tear down its SES identity + DNS / receipt-rule recipient. Returns a + `cleanup_note` when the removed domain was Seclai-managed (delegated), reminding the caller to + delete the registrar NS delegation record (dangling-delegation / subdomain-takeover guard). + Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | RemoveEmailDomainResponse + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/send_test_email_api_api_email_domains_domain_id_test_email_post.py b/seclai/_generated/api/email_domains/send_test_email_api_api_email_domains_domain_id_test_email_post.py new file mode 100644 index 0000000..60de6b8 --- /dev/null +++ b/seclai/_generated/api/email_domains/send_test_email_api_api_email_domains_domain_id_test_email_post.py @@ -0,0 +1,208 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...models.send_test_email_response import SendTestEmailResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: UUID, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/email-domains/{domain_id}/test-email".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | SendTestEmailResponse | None: + if response.status_code == 200: + response_200 = SendTestEmailResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | SendTestEmailResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | SendTestEmailResponse]: + """Send a test email from a verified domain to the account owner + + Send a test message FROM the verified domain (`noreply@`) TO the account owner's email, to + confirm end-to-end that the domain actually sends (SES verified, DKIM/SPF aligned). Never sends to + any other address, so it can't be used as an open relay. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | SendTestEmailResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | SendTestEmailResponse | None: + """Send a test email from a verified domain to the account owner + + Send a test message FROM the verified domain (`noreply@`) TO the account owner's email, to + confirm end-to-end that the domain actually sends (SES verified, DKIM/SPF aligned). Never sends to + any other address, so it can't be used as an open relay. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | SendTestEmailResponse + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[HTTPValidationError | SendTestEmailResponse]: + """Send a test email from a verified domain to the account owner + + Send a test message FROM the verified domain (`noreply@`) TO the account owner's email, to + confirm end-to-end that the domain actually sends (SES verified, DKIM/SPF aligned). Never sends to + any other address, so it can't be used as an open relay. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | SendTestEmailResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> HTTPValidationError | SendTestEmailResponse | None: + """Send a test email from a verified domain to the account owner + + Send a test message FROM the verified domain (`noreply@`) TO the account owner's email, to + confirm end-to-end that the domain actually sends (SES verified, DKIM/SPF aligned). Never sends to + any other address, so it can't be used as an open relay. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | SendTestEmailResponse + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/set_primary_email_domain_api_api_email_domains_domain_id_primary_post.py b/seclai/_generated/api/email_domains/set_primary_email_domain_api_api_email_domains_domain_id_primary_post.py new file mode 100644 index 0000000..9cbaf40 --- /dev/null +++ b/seclai/_generated/api/email_domains/set_primary_email_domain_api_api_email_domains_domain_id_primary_post.py @@ -0,0 +1,208 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.email_domain_response import EmailDomainResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: UUID, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/email-domains/{domain_id}/primary".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> EmailDomainResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = EmailDomainResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[EmailDomainResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Make a verified domain the account's primary sending/inbound domain + + Promote a verified domain to the account's primary domain — agent email then sends FROM and receives + ON this domain (`@`, `@`) instead of the shared `agent.seclai.com`. + The domain must be verified. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Make a verified domain the account's primary sending/inbound domain + + Promote a verified domain to the account's primary domain — agent email then sends FROM and receives + ON this domain (`@`, `@`) instead of the shared `agent.seclai.com`. + The domain must be verified. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Make a verified domain the account's primary sending/inbound domain + + Promote a verified domain to the account's primary domain — agent email then sends FROM and receives + ON this domain (`@`, `@`) instead of the shared `agent.seclai.com`. + The domain must be verified. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Make a verified domain the account's primary sending/inbound domain + + Promote a verified domain to the account's primary domain — agent email then sends FROM and receives + ON this domain (`@`, `@`) instead of the shared `agent.seclai.com`. + The domain must be verified. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/email_domains/use_shared_domain_api_api_email_domains_use_shared_domain_post.py b/seclai/_generated/api/email_domains/use_shared_domain_api_api_email_domains_use_shared_domain_post.py new file mode 100644 index 0000000..8cc88d1 --- /dev/null +++ b/seclai/_generated/api/email_domains/use_shared_domain_api_api_email_domains_use_shared_domain_post.py @@ -0,0 +1,119 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/email-domains/use-shared-domain", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Any | None: + if response.status_code == 204: + return None + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any]: + """Revert to the shared agent.seclai.com sending domain + + Clear the account's primary domain so agent email reverts to the shared `agent.seclai.com` + sending/inbound scheme, WITHOUT removing the configured domain(s) — they stay verified and can be + promoted again later. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[Any]: + """Revert to the shared agent.seclai.com sending domain + + Clear the account's primary domain so agent email reverts to the shared `agent.seclai.com` + sending/inbound scheme, WITHOUT removing the configured domain(s) — they stay verified and can be + promoted again later. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) diff --git a/seclai/_generated/api/email_domains/verify_email_domain_api_api_email_domains_domain_id_verify_post.py b/seclai/_generated/api/email_domains/verify_email_domain_api_api_email_domains_domain_id_verify_post.py new file mode 100644 index 0000000..7813280 --- /dev/null +++ b/seclai/_generated/api/email_domains/verify_email_domain_api_api_email_domains_domain_id_verify_post.py @@ -0,0 +1,208 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.email_domain_response import EmailDomainResponse +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: UUID, + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/email-domains/{domain_id}/verify".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> EmailDomainResponse | HTTPValidationError | None: + if response.status_code == 200: + response_200 = EmailDomainResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[EmailDomainResponse | HTTPValidationError]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Run a verification check immediately ('Check now') + + Re-poll SES + DNS for this domain right now instead of waiting for the background verification + sweep, and return its updated status + DNS-record check results. Useful right after publishing the + required records. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Run a verification check immediately ('Check now') + + Re-poll SES + DNS for this domain right now instead of waiting for the background verification + sweep, and return its updated status + DNS-record check results. Useful right after publishing the + required records. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[EmailDomainResponse | HTTPValidationError]: + """Run a verification check immediately ('Check now') + + Re-poll SES + DNS for this domain right now instead of waiting for the background verification + sweep, and return its updated status + DNS-record check results. Useful right after publishing the + required records. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[EmailDomainResponse | HTTPValidationError] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: UUID, + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> EmailDomainResponse | HTTPValidationError | None: + """Run a verification check immediately ('Check now') + + Re-poll SES + DNS for this domain right now instead of waiting for the background verification + sweep, and return its updated status + DNS-record check results. Useful right after publishing the + required records. Owner/admin only. + + Auth & scoping: requires an `X-API-Key` header or OAuth Bearer token bound to a **user** (an + account-only key is refused with 403); the domain is scoped to the key's account. + + Args: + domain_id (UUID): + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + EmailDomainResponse | HTTPValidationError + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/models/get_generation_tiers_api_models_generation_tiers_get.py b/seclai/_generated/api/models/get_generation_tiers_api_models_generation_tiers_get.py new file mode 100644 index 0000000..ba6c94f --- /dev/null +++ b/seclai/_generated/api/models/get_generation_tiers_api_models_generation_tiers_get.py @@ -0,0 +1,221 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get import ( + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet, +) +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/models/generation-tiers", + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ( + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet + | None +): + if response.status_code == 200: + response_200 = GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet.from_dict( + response.json() + ) + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet +]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[ + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet +]: + """Get Generation Tiers + + List the media-generation quality tiers and the model + cost each resolves to. + + On a prompt_call's `media_generation` tool — and the dedicated generate_* steps via tier routing — + the author/LLM chooses a *tier* (fast/balanced/thorough), never a model. This is the surface that + maps each `(modality, tier)` to its concrete generator, raw `credits_per_unit`, `unit_label`, and a + human-readable scaled `price_label`. Global routing/pricing (the same for every account); read-only. + REST parity with the `list_generation_tiers` MCP tool. + + Auth & scoping: + - Requires `X-API-Key` header or OAuth Bearer token. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> ( + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet + | None +): + """Get Generation Tiers + + List the media-generation quality tiers and the model + cost each resolves to. + + On a prompt_call's `media_generation` tool — and the dedicated generate_* steps via tier routing — + the author/LLM chooses a *tier* (fast/balanced/thorough), never a model. This is the surface that + maps each `(modality, tier)` to its concrete generator, raw `credits_per_unit`, `unit_label`, and a + human-readable scaled `price_label`. Global routing/pricing (the same for every account); read-only. + REST parity with the `list_generation_tiers` MCP tool. + + Auth & scoping: + - Requires `X-API-Key` header or OAuth Bearer token. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet + """ + + return sync_detailed( + client=client, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> Response[ + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet +]: + """Get Generation Tiers + + List the media-generation quality tiers and the model + cost each resolves to. + + On a prompt_call's `media_generation` tool — and the dedicated generate_* steps via tier routing — + the author/LLM chooses a *tier* (fast/balanced/thorough), never a model. This is the surface that + maps each `(modality, tier)` to its concrete generator, raw `credits_per_unit`, `unit_label`, and a + human-readable scaled `price_label`. Global routing/pricing (the same for every account); read-only. + REST parity with the `list_generation_tiers` MCP tool. + + Auth & scoping: + - Requires `X-API-Key` header or OAuth Bearer token. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet] + """ + + kwargs = _get_kwargs( + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + x_account_id: UUID | Unset = UNSET, +) -> ( + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet + | None +): + """Get Generation Tiers + + List the media-generation quality tiers and the model + cost each resolves to. + + On a prompt_call's `media_generation` tool — and the dedicated generate_* steps via tier routing — + the author/LLM chooses a *tier* (fast/balanced/thorough), never a model. This is the surface that + maps each `(modality, tier)` to its concrete generator, raw `credits_per_unit`, `unit_label`, and a + human-readable scaled `price_label`. Global routing/pricing (the same for every account); read-only. + REST parity with the `list_generation_tiers` MCP tool. + + Auth & scoping: + - Requires `X-API-Key` header or OAuth Bearer token. + + Args: + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet + """ + + return ( + await asyncio_detailed( + client=client, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/models/list_models_api_models_get.py b/seclai/_generated/api/models/list_models_api_models_get.py index 2d6092d..d57c40e 100644 --- a/seclai/_generated/api/models/list_models_api_models_get.py +++ b/seclai/_generated/api/models/list_models_api_models_get.py @@ -16,6 +16,8 @@ def _get_kwargs( provider: None | str | Unset = UNSET, supports_tool_use: bool | None | Unset = UNSET, supports_thinking: bool | None | Unset = UNSET, + supports_input_media: None | str | Unset = UNSET, + supports_output_media: None | str | Unset = UNSET, x_account_id: UUID | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} @@ -45,6 +47,20 @@ def _get_kwargs( json_supports_thinking = supports_thinking params["supports_thinking"] = json_supports_thinking + json_supports_input_media: None | str | Unset + if isinstance(supports_input_media, Unset): + json_supports_input_media = UNSET + else: + json_supports_input_media = supports_input_media + params["supports_input_media"] = json_supports_input_media + + json_supports_output_media: None | str | Unset + if isinstance(supports_output_media, Unset): + json_supports_output_media = UNSET + else: + json_supports_output_media = supports_output_media + params["supports_output_media"] = json_supports_output_media + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} _kwargs: dict[str, Any] = { @@ -98,6 +114,8 @@ def sync_detailed( provider: None | str | Unset = UNSET, supports_tool_use: bool | None | Unset = UNSET, supports_thinking: bool | None | Unset = UNSET, + supports_input_media: None | str | Unset = UNSET, + supports_output_media: None | str | Unset = UNSET, x_account_id: UUID | Unset = UNSET, ) -> Response[HTTPValidationError | list[ProviderGroupResponse]]: """List Models @@ -111,6 +129,10 @@ def sync_detailed( - `provider`: filter by provider (e.g. 'anthropic', 'openai') - `supports_tool_use`: filter to models with tool calling support - `supports_thinking`: filter to models with extended thinking support + - `supports_input_media`: filter to models accepting an input modality + (`image`/`audio`/`video`/`pdf` or a full MIME) + - `supports_output_media`: filter to models producing an output modality (`image`/`audio`/`video`) — + e.g. to find generation models Auth & scoping: - Requires `X-API-Key` header or OAuth Bearer token. @@ -119,6 +141,11 @@ def sync_detailed( provider (None | str | Unset): Filter by provider name supports_tool_use (bool | None | Unset): Filter to models that support tool use supports_thinking (bool | None | Unset): Filter to models that support extended thinking + supports_input_media (None | str | Unset): Filter to models that accept this input + modality — a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME. + supports_output_media (None | str | Unset): Filter to models that produce this output + modality — a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find + image/audio/video generation models. x_account_id (UUID | Unset): Raises: @@ -133,6 +160,8 @@ def sync_detailed( provider=provider, supports_tool_use=supports_tool_use, supports_thinking=supports_thinking, + supports_input_media=supports_input_media, + supports_output_media=supports_output_media, x_account_id=x_account_id, ) @@ -149,6 +178,8 @@ def sync( provider: None | str | Unset = UNSET, supports_tool_use: bool | None | Unset = UNSET, supports_thinking: bool | None | Unset = UNSET, + supports_input_media: None | str | Unset = UNSET, + supports_output_media: None | str | Unset = UNSET, x_account_id: UUID | Unset = UNSET, ) -> HTTPValidationError | list[ProviderGroupResponse] | None: """List Models @@ -162,6 +193,10 @@ def sync( - `provider`: filter by provider (e.g. 'anthropic', 'openai') - `supports_tool_use`: filter to models with tool calling support - `supports_thinking`: filter to models with extended thinking support + - `supports_input_media`: filter to models accepting an input modality + (`image`/`audio`/`video`/`pdf` or a full MIME) + - `supports_output_media`: filter to models producing an output modality (`image`/`audio`/`video`) — + e.g. to find generation models Auth & scoping: - Requires `X-API-Key` header or OAuth Bearer token. @@ -170,6 +205,11 @@ def sync( provider (None | str | Unset): Filter by provider name supports_tool_use (bool | None | Unset): Filter to models that support tool use supports_thinking (bool | None | Unset): Filter to models that support extended thinking + supports_input_media (None | str | Unset): Filter to models that accept this input + modality — a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME. + supports_output_media (None | str | Unset): Filter to models that produce this output + modality — a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find + image/audio/video generation models. x_account_id (UUID | Unset): Raises: @@ -185,6 +225,8 @@ def sync( provider=provider, supports_tool_use=supports_tool_use, supports_thinking=supports_thinking, + supports_input_media=supports_input_media, + supports_output_media=supports_output_media, x_account_id=x_account_id, ).parsed @@ -195,6 +237,8 @@ async def asyncio_detailed( provider: None | str | Unset = UNSET, supports_tool_use: bool | None | Unset = UNSET, supports_thinking: bool | None | Unset = UNSET, + supports_input_media: None | str | Unset = UNSET, + supports_output_media: None | str | Unset = UNSET, x_account_id: UUID | Unset = UNSET, ) -> Response[HTTPValidationError | list[ProviderGroupResponse]]: """List Models @@ -208,6 +252,10 @@ async def asyncio_detailed( - `provider`: filter by provider (e.g. 'anthropic', 'openai') - `supports_tool_use`: filter to models with tool calling support - `supports_thinking`: filter to models with extended thinking support + - `supports_input_media`: filter to models accepting an input modality + (`image`/`audio`/`video`/`pdf` or a full MIME) + - `supports_output_media`: filter to models producing an output modality (`image`/`audio`/`video`) — + e.g. to find generation models Auth & scoping: - Requires `X-API-Key` header or OAuth Bearer token. @@ -216,6 +264,11 @@ async def asyncio_detailed( provider (None | str | Unset): Filter by provider name supports_tool_use (bool | None | Unset): Filter to models that support tool use supports_thinking (bool | None | Unset): Filter to models that support extended thinking + supports_input_media (None | str | Unset): Filter to models that accept this input + modality — a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME. + supports_output_media (None | str | Unset): Filter to models that produce this output + modality — a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find + image/audio/video generation models. x_account_id (UUID | Unset): Raises: @@ -230,6 +283,8 @@ async def asyncio_detailed( provider=provider, supports_tool_use=supports_tool_use, supports_thinking=supports_thinking, + supports_input_media=supports_input_media, + supports_output_media=supports_output_media, x_account_id=x_account_id, ) @@ -244,6 +299,8 @@ async def asyncio( provider: None | str | Unset = UNSET, supports_tool_use: bool | None | Unset = UNSET, supports_thinking: bool | None | Unset = UNSET, + supports_input_media: None | str | Unset = UNSET, + supports_output_media: None | str | Unset = UNSET, x_account_id: UUID | Unset = UNSET, ) -> HTTPValidationError | list[ProviderGroupResponse] | None: """List Models @@ -257,6 +314,10 @@ async def asyncio( - `provider`: filter by provider (e.g. 'anthropic', 'openai') - `supports_tool_use`: filter to models with tool calling support - `supports_thinking`: filter to models with extended thinking support + - `supports_input_media`: filter to models accepting an input modality + (`image`/`audio`/`video`/`pdf` or a full MIME) + - `supports_output_media`: filter to models producing an output modality (`image`/`audio`/`video`) — + e.g. to find generation models Auth & scoping: - Requires `X-API-Key` header or OAuth Bearer token. @@ -265,6 +326,11 @@ async def asyncio( provider (None | str | Unset): Filter by provider name supports_tool_use (bool | None | Unset): Filter to models that support tool use supports_thinking (bool | None | Unset): Filter to models that support extended thinking + supports_input_media (None | str | Unset): Filter to models that accept this input + modality — a coarse kind (`image`, `audio`, `video`, `pdf`) or a full MIME. + supports_output_media (None | str | Unset): Filter to models that produce this output + modality — a coarse kind (`image`, `audio`, `video`) or a full MIME. Use to find + image/audio/video generation models. x_account_id (UUID | Unset): Raises: @@ -281,6 +347,8 @@ async def asyncio( provider=provider, supports_tool_use=supports_tool_use, supports_thinking=supports_thinking, + supports_input_media=supports_input_media, + supports_output_media=supports_output_media, x_account_id=x_account_id, ) ).parsed diff --git a/seclai/_generated/api/search/docs_search_api_docs_search_get.py b/seclai/_generated/api/search/docs_search_api_docs_search_get.py new file mode 100644 index 0000000..5e521a2 --- /dev/null +++ b/seclai/_generated/api/search/docs_search_api_docs_search_get.py @@ -0,0 +1,287 @@ +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.docs_search_api_docs_search_get_mode import ( + DocsSearchApiDocsSearchGetMode, +) +from ...models.docs_search_api_docs_search_get_response_docs_search_api_docs_search_get import ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet, +) +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + q: str, + mode: ( + DocsSearchApiDocsSearchGetMode | Unset + ) = DocsSearchApiDocsSearchGetMode.KEYWORD, + limit: int | Unset = 8, + x_account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(x_account_id, Unset): + headers["X-Account-Id"] = x_account_id + + params: dict[str, Any] = {} + + params["q"] = q + + json_mode: str | Unset = UNSET + if not isinstance(mode, Unset): + json_mode = mode.value + + params["mode"] = json_mode + + params["limit"] = limit + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/docs-search", + "params": params, + } + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet + | HTTPValidationError + | None +): + if response.status_code == 200: + response_200 = ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet.from_dict( + response.json() + ) + ) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError +]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + q: str, + mode: ( + DocsSearchApiDocsSearchGetMode | Unset + ) = DocsSearchApiDocsSearchGetMode.KEYWORD, + limit: int | Unset = 8, + x_account_id: UUID | Unset = UNSET, +) -> Response[ + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError +]: + """Search documentation + + Search the Seclai documentation by content and return matching pages. `mode=keyword` matches page + titles and summaries (fast, no AI cost); `mode=semantic` matches page body content by meaning (uses + an embedding). Each result carries a `doc_slug` and an optional section `anchor` for building a + `https://seclai.com/docs/[#]` link, a `score` (relevance; not comparable across + modes), and — in semantic mode — a `highlight` (best matching verbatim sentence; `null` for + keyword). Documentation is global, so results are not account-scoped. + + Args: + q (str): Search query + mode (DocsSearchApiDocsSearchGetMode | Unset): Search strategy Default: + DocsSearchApiDocsSearchGetMode.KEYWORD. + limit (int | Unset): Maximum results Default: 8. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError] + """ + + kwargs = _get_kwargs( + q=q, + mode=mode, + limit=limit, + x_account_id=x_account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + q: str, + mode: ( + DocsSearchApiDocsSearchGetMode | Unset + ) = DocsSearchApiDocsSearchGetMode.KEYWORD, + limit: int | Unset = 8, + x_account_id: UUID | Unset = UNSET, +) -> ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet + | HTTPValidationError + | None +): + """Search documentation + + Search the Seclai documentation by content and return matching pages. `mode=keyword` matches page + titles and summaries (fast, no AI cost); `mode=semantic` matches page body content by meaning (uses + an embedding). Each result carries a `doc_slug` and an optional section `anchor` for building a + `https://seclai.com/docs/[#]` link, a `score` (relevance; not comparable across + modes), and — in semantic mode — a `highlight` (best matching verbatim sentence; `null` for + keyword). Documentation is global, so results are not account-scoped. + + Args: + q (str): Search query + mode (DocsSearchApiDocsSearchGetMode | Unset): Search strategy Default: + DocsSearchApiDocsSearchGetMode.KEYWORD. + limit (int | Unset): Maximum results Default: 8. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError + """ + + return sync_detailed( + client=client, + q=q, + mode=mode, + limit=limit, + x_account_id=x_account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + q: str, + mode: ( + DocsSearchApiDocsSearchGetMode | Unset + ) = DocsSearchApiDocsSearchGetMode.KEYWORD, + limit: int | Unset = 8, + x_account_id: UUID | Unset = UNSET, +) -> Response[ + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError +]: + """Search documentation + + Search the Seclai documentation by content and return matching pages. `mode=keyword` matches page + titles and summaries (fast, no AI cost); `mode=semantic` matches page body content by meaning (uses + an embedding). Each result carries a `doc_slug` and an optional section `anchor` for building a + `https://seclai.com/docs/[#]` link, a `score` (relevance; not comparable across + modes), and — in semantic mode — a `highlight` (best matching verbatim sentence; `null` for + keyword). Documentation is global, so results are not account-scoped. + + Args: + q (str): Search query + mode (DocsSearchApiDocsSearchGetMode | Unset): Search strategy Default: + DocsSearchApiDocsSearchGetMode.KEYWORD. + limit (int | Unset): Maximum results Default: 8. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError] + """ + + kwargs = _get_kwargs( + q=q, + mode=mode, + limit=limit, + x_account_id=x_account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + q: str, + mode: ( + DocsSearchApiDocsSearchGetMode | Unset + ) = DocsSearchApiDocsSearchGetMode.KEYWORD, + limit: int | Unset = 8, + x_account_id: UUID | Unset = UNSET, +) -> ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet + | HTTPValidationError + | None +): + """Search documentation + + Search the Seclai documentation by content and return matching pages. `mode=keyword` matches page + titles and summaries (fast, no AI cost); `mode=semantic` matches page body content by meaning (uses + an embedding). Each result carries a `doc_slug` and an optional section `anchor` for building a + `https://seclai.com/docs/[#]` link, a `score` (relevance; not comparable across + modes), and — in semantic mode — a `highlight` (best matching verbatim sentence; `null` for + keyword). Documentation is global, so results are not account-scoped. + + Args: + q (str): Search query + mode (DocsSearchApiDocsSearchGetMode | Unset): Search strategy Default: + DocsSearchApiDocsSearchGetMode.KEYWORD. + limit (int | Unset): Maximum results Default: 8. + x_account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet | HTTPValidationError + """ + + return ( + await asyncio_detailed( + client=client, + q=q, + mode=mode, + limit=limit, + x_account_id=x_account_id, + ) + ).parsed diff --git a/seclai/_generated/api/search/search_api_search_get.py b/seclai/_generated/api/search/search_api_search_get.py index c5141cf..90835f9 100644 --- a/seclai/_generated/api/search/search_api_search_get.py +++ b/seclai/_generated/api/search/search_api_search_get.py @@ -95,7 +95,7 @@ def sync_detailed( UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, - governance_policy. + governance_policy, mcp_client. Args: q (str): Search query @@ -140,7 +140,7 @@ def sync( UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, - governance_policy. + governance_policy, mcp_client. Args: q (str): Search query @@ -180,7 +180,7 @@ async def asyncio_detailed( UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, - governance_policy. + governance_policy, mcp_client. Args: q (str): Search query @@ -223,7 +223,7 @@ async def asyncio( UUIDs are matched exactly; keywords are matched by name and description (case-insensitive substring). Results are ranked: name-prefix > name-substring > description-substring. Searchable types: agent, knowledge_base, source_connection, solution, memory_bank, alert, api_key, - governance_policy. + governance_policy, mcp_client. Args: q (str): Search query diff --git a/seclai/_generated/models/__init__.py b/seclai/_generated/models/__init__.py index e748ecb..c7228ad 100644 --- a/seclai/_generated/models/__init__.py +++ b/seclai/_generated/models/__init__.py @@ -8,13 +8,17 @@ from .add_conversation_turn_request_actions_taken_type_0 import ( AddConversationTurnRequestActionsTakenType0, ) +from .add_email_domain_request import AddEmailDomainRequest from .agent_attachment_refs_api_response import AgentAttachmentRefsApiResponse +from .agent_caller_api_response import AgentCallerApiResponse from .agent_definition_import_error_response import AgentDefinitionImportErrorResponse from .agent_definition_response import AgentDefinitionResponse from .agent_definition_response_definition import AgentDefinitionResponseDefinition from .agent_definition_response_warnings_type_0_item import ( AgentDefinitionResponseWarningsType0Item, ) +from .agent_email_opt_out_list_response import AgentEmailOptOutListResponse +from .agent_email_opt_out_response import AgentEmailOptOutResponse from .agent_evaluation_tier import AgentEvaluationTier from .agent_export_response import AgentExportResponse from .agent_export_response_agent import AgentExportResponseAgent @@ -75,6 +79,9 @@ ) from .applied_action_response import AppliedActionResponse from .attachment_refs_source_api_summary import AttachmentRefsSourceApiSummary +from .block_email_sender_request import BlockEmailSenderRequest +from .blocked_email_sender_list_response import BlockedEmailSenderListResponse +from .blocked_email_sender_response import BlockedEmailSenderResponse from .body_upload_file_to_content_api_contents_source_connection_content_version_upload_post import ( BodyUploadFileToContentApiContentsSourceConnectionContentVersionUploadPost, ) @@ -84,6 +91,7 @@ from .cancel_experiment_endpoint_api_models_playground_experiments_experiment_id_cancel_post_response_cancel_experiment_endpoint_api_models_playground_experiments_experiment_id_cancel_post import ( CancelExperimentEndpointApiModelsPlaygroundExperimentsExperimentIdCancelPostResponseCancelExperimentEndpointApiModelsPlaygroundExperimentsExperimentIdCancelPost, ) +from .cancel_queued_runs_response import CancelQueuedRunsResponse from .change_alert_status_api_alerts_alert_id_status_post_response_change_alert_status_api_alerts_alert_id_status_post import ( ChangeAlertStatusApiAlertsAlertIdStatusPostResponseChangeAlertStatusApiAlertsAlertIdStatusPost, ) @@ -132,6 +140,18 @@ from .create_memory_bank_body import CreateMemoryBankBody from .create_solution_request import CreateSolutionRequest from .create_source_body import CreateSourceBody +from .dmarc_failing_source_response import DmarcFailingSourceResponse +from .dmarc_summary_response import DmarcSummaryResponse +from .dmarc_summary_response_dispositions import DmarcSummaryResponseDispositions +from .dns_provider_response import DnsProviderResponse +from .dns_record_response import DnsRecordResponse +from .docs_search_api_docs_search_get_mode import DocsSearchApiDocsSearchGetMode +from .docs_search_api_docs_search_get_response_docs_search_api_docs_search_get import ( + DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet, +) +from .email_domain_response import EmailDomainResponse +from .email_domains_list_response import EmailDomainsListResponse +from .email_trigger_config_response import EmailTriggerConfigResponse from .estimate_export_request import EstimateExportRequest from .estimate_export_request_metadata_filter_type_0 import ( EstimateExportRequestMetadataFilterType0, @@ -206,6 +226,9 @@ from .get_experiment_api_models_playground_experiments_experiment_id_get_response_get_experiment_api_models_playground_experiments_experiment_id_get import ( GetExperimentApiModelsPlaygroundExperimentsExperimentIdGetResponseGetExperimentApiModelsPlaygroundExperimentsExperimentIdGet, ) +from .get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get import ( + GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet, +) from .get_memory_bank_entry_stats_api_memory_banks_memory_bank_id_stats_get_response_get_memory_bank_entry_stats_api_memory_banks_memory_bank_id_stats_get import ( GetMemoryBankEntryStatsApiMemoryBanksMemoryBankIdStatsGetResponseGetMemoryBankEntryStatsApiMemoryBanksMemoryBankIdStatsGet, ) @@ -224,6 +247,8 @@ from .import_field_error_model import ImportFieldErrorModel from .import_skip_response import ImportSkipResponse from .import_skip_response_details import ImportSkipResponseDetails +from .inbound_email_rejection_response import InboundEmailRejectionResponse +from .inbound_email_status_response import InboundEmailStatusResponse from .inline_text_replace_request import InlineTextReplaceRequest from .inline_text_replace_request_metadata_type_0 import ( InlineTextReplaceRequestMetadataType0, @@ -298,6 +323,9 @@ ) from .prompt_model_auto_upgrade_strategy import PromptModelAutoUpgradeStrategy from .prompt_model_response import PromptModelResponse +from .prompt_model_response_generation_params_type_0 import ( + PromptModelResponseGenerationParamsType0, +) from .prompt_model_response_payload_schema_type_0 import ( PromptModelResponsePayloadSchemaType0, ) @@ -309,9 +337,14 @@ from .proposed_policy_action_response import ProposedPolicyActionResponse from .proposed_policy_action_response_params import ProposedPolicyActionResponseParams from .provider_group_response import ProviderGroupResponse +from .remove_email_domain_response import RemoveEmailDomainResponse +from .resume_inbound_response import ResumeInboundResponse from .search_api_search_get_response_search_api_search_get import ( SearchApiSearchGetResponseSearchApiSearchGet, ) +from .send_test_email_response import SendTestEmailResponse +from .set_auto_block_mode_request import SetAutoBlockModeRequest +from .set_email_trigger_config_request import SetEmailTriggerConfigRequest from .solution_agent_response import SolutionAgentResponse from .solution_conversation_response import SolutionConversationResponse from .solution_conversation_response_actions_taken_type_0 import ( @@ -384,11 +417,15 @@ "AddCommentRequest", "AddConversationTurnRequest", "AddConversationTurnRequestActionsTakenType0", + "AddEmailDomainRequest", "AgentAttachmentRefsApiResponse", + "AgentCallerApiResponse", "AgentDefinitionImportErrorResponse", "AgentDefinitionResponse", "AgentDefinitionResponseDefinition", "AgentDefinitionResponseWarningsType0Item", + "AgentEmailOptOutListResponse", + "AgentEmailOptOutResponse", "AgentEvaluationTier", "AgentExportResponse", "AgentExportResponseAgent", @@ -429,9 +466,13 @@ "ApiAiMemoryBankAcceptApiAiAssistantMemoryBankConversationIdPatchResponseApiAiMemoryBankAcceptApiAiAssistantMemoryBankConversationIdPatch", "AppliedActionResponse", "AttachmentRefsSourceApiSummary", + "BlockedEmailSenderListResponse", + "BlockedEmailSenderResponse", + "BlockEmailSenderRequest", "BodyUploadFileToContentApiContentsSourceConnectionContentVersionUploadPost", "BodyUploadFileToSourceApiSourcesSourceConnectionIdUploadPost", "CancelExperimentEndpointApiModelsPlaygroundExperimentsExperimentIdCancelPostResponseCancelExperimentEndpointApiModelsPlaygroundExperimentsExperimentIdCancelPost", + "CancelQueuedRunsResponse", "ChangeAlertStatusApiAlertsAlertIdStatusPostResponseChangeAlertStatusApiAlertsAlertIdStatusPost", "ChangeStatusRequest", "CompactionEvaluationModel", @@ -460,6 +501,16 @@ "CreateMemoryBankBody", "CreateSolutionRequest", "CreateSourceBody", + "DmarcFailingSourceResponse", + "DmarcSummaryResponse", + "DmarcSummaryResponseDispositions", + "DnsProviderResponse", + "DnsRecordResponse", + "DocsSearchApiDocsSearchGetMode", + "DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet", + "EmailDomainResponse", + "EmailDomainsListResponse", + "EmailTriggerConfigResponse", "EstimateExportRequest", "EstimateExportRequestMetadataFilterType0", "EstimateExportResponse", @@ -498,6 +549,7 @@ "GetAlertDetailApiAlertsAlertIdGetResponseGetAlertDetailApiAlertsAlertIdGet", "GetAlertUnreadCountApiModelsAlertsUnreadCountGetResponseGetAlertUnreadCountApiModelsAlertsUnreadCountGet", "GetExperimentApiModelsPlaygroundExperimentsExperimentIdGetResponseGetExperimentApiModelsPlaygroundExperimentsExperimentIdGet", + "GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet", "GetMemoryBankEntryStatsApiMemoryBanksMemoryBankIdStatsGetResponseGetMemoryBankEntryStatsApiMemoryBanksMemoryBankIdStatsGet", "GetRecommendationsApiModelsModelIdRecommendationsGetResponseGetRecommendationsApiModelsModelIdRecommendationsGet", "GovernanceAiAcceptResponse", @@ -510,6 +562,8 @@ "ImportFieldErrorModel", "ImportSkipResponse", "ImportSkipResponseDetails", + "InboundEmailRejectionResponse", + "InboundEmailStatusResponse", "InlineTextReplaceRequest", "InlineTextReplaceRequestMetadataType0", "InlineTextUploadRequest", @@ -552,6 +606,7 @@ "PlaygroundCreateRequestEvaluationMode", "PromptModelAutoUpgradeStrategy", "PromptModelResponse", + "PromptModelResponseGenerationParamsType0", "PromptModelResponsePayloadSchemaType0", "PromptToolResponse", "PromptToolResponseHeadersType0", @@ -561,7 +616,12 @@ "ProposedPolicyActionResponse", "ProposedPolicyActionResponseParams", "ProviderGroupResponse", + "RemoveEmailDomainResponse", + "ResumeInboundResponse", "SearchApiSearchGetResponseSearchApiSearchGet", + "SendTestEmailResponse", + "SetAutoBlockModeRequest", + "SetEmailTriggerConfigRequest", "SolutionAgentResponse", "SolutionConversationResponse", "SolutionConversationResponseActionsTakenType0", diff --git a/seclai/_generated/models/add_email_domain_request.py b/seclai/_generated/models/add_email_domain_request.py new file mode 100644 index 0000000..475eff0 --- /dev/null +++ b/seclai/_generated/models/add_email_domain_request.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AddEmailDomainRequest") + + +@_attrs_define +class AddEmailDomainRequest: + """ + Attributes: + kind (str): 'vanity' or 'custom' + value (str): + delegated (bool | Unset): Default: False. + """ + + kind: str + value: str + delegated: bool | Unset = False + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + kind = self.kind + + value = self.value + + delegated = self.delegated + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "kind": kind, + "value": value, + } + ) + if delegated is not UNSET: + field_dict["delegated"] = delegated + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + kind = d.pop("kind") + + value = d.pop("value") + + delegated = d.pop("delegated", UNSET) + + add_email_domain_request = cls( + kind=kind, + value=value, + delegated=delegated, + ) + + add_email_domain_request.additional_properties = d + return add_email_domain_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/agent_caller_api_response.py b/seclai/_generated/models/agent_caller_api_response.py new file mode 100644 index 0000000..6df371b --- /dev/null +++ b/seclai/_generated/models/agent_caller_api_response.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="AgentCallerApiResponse") + + +@_attrs_define +class AgentCallerApiResponse: + """One agent that calls another (blocks disabling the callee while live). + + Attributes: + disabled (bool): + id (UUID): + name (str): + """ + + disabled: bool + id: UUID + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + disabled = self.disabled + + id = str(self.id) + + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "disabled": disabled, + "id": id, + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + disabled = d.pop("disabled") + + id = UUID(d.pop("id")) + + name = d.pop("name") + + agent_caller_api_response = cls( + disabled=disabled, + id=id, + name=name, + ) + + agent_caller_api_response.additional_properties = d + return agent_caller_api_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/agent_email_opt_out_list_response.py b/seclai/_generated/models/agent_email_opt_out_list_response.py new file mode 100644 index 0000000..8bf6dcd --- /dev/null +++ b/seclai/_generated/models/agent_email_opt_out_list_response.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.agent_email_opt_out_response import AgentEmailOptOutResponse + + +T = TypeVar("T", bound="AgentEmailOptOutListResponse") + + +@_attrs_define +class AgentEmailOptOutListResponse: + """A page of agent-email opt-outs plus the total (for pagination). + + Attributes: + items (list[AgentEmailOptOutResponse]): + total (int): + """ + + items: list[AgentEmailOptOutResponse] + total: int + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + items = [] + for items_item_data in self.items: + items_item = items_item_data.to_dict() + items.append(items_item) + + total = self.total + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "items": items, + "total": total, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.agent_email_opt_out_response import AgentEmailOptOutResponse + + d = dict(src_dict) + items = [] + _items = d.pop("items") + for items_item_data in _items: + items_item = AgentEmailOptOutResponse.from_dict(items_item_data) + + items.append(items_item) + + total = d.pop("total") + + agent_email_opt_out_list_response = cls( + items=items, + total=total, + ) + + agent_email_opt_out_list_response.additional_properties = d + return agent_email_opt_out_list_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/agent_email_opt_out_response.py b/seclai/_generated/models/agent_email_opt_out_response.py new file mode 100644 index 0000000..eedca64 --- /dev/null +++ b/seclai/_generated/models/agent_email_opt_out_response.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="AgentEmailOptOutResponse") + + +@_attrs_define +class AgentEmailOptOutResponse: + """A recipient's opt-out from an account's agent emails (one agent or all). + + Attributes: + agent_id (None | UUID): + agent_name (None | str): + comment (None | str): + created_at (str): + id (UUID): + reason (None | str): + recipient_email (str): + """ + + agent_id: None | UUID + agent_name: None | str + comment: None | str + created_at: str + id: UUID + reason: None | str + recipient_email: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + agent_id: None | str + if isinstance(self.agent_id, UUID): + agent_id = str(self.agent_id) + else: + agent_id = self.agent_id + + agent_name: None | str + agent_name = self.agent_name + + comment: None | str + comment = self.comment + + created_at = self.created_at + + id = str(self.id) + + reason: None | str + reason = self.reason + + recipient_email = self.recipient_email + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "agent_id": agent_id, + "agent_name": agent_name, + "comment": comment, + "created_at": created_at, + "id": id, + "reason": reason, + "recipient_email": recipient_email, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + + def _parse_agent_id(data: object) -> None | UUID: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + agent_id_type_0 = UUID(data) + + return agent_id_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | UUID, data) + + agent_id = _parse_agent_id(d.pop("agent_id")) + + def _parse_agent_name(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + agent_name = _parse_agent_name(d.pop("agent_name")) + + def _parse_comment(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + comment = _parse_comment(d.pop("comment")) + + created_at = d.pop("created_at") + + id = UUID(d.pop("id")) + + def _parse_reason(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + reason = _parse_reason(d.pop("reason")) + + recipient_email = d.pop("recipient_email") + + agent_email_opt_out_response = cls( + agent_id=agent_id, + agent_name=agent_name, + comment=comment, + created_at=created_at, + id=id, + reason=reason, + recipient_email=recipient_email, + ) + + agent_email_opt_out_response.additional_properties = d + return agent_email_opt_out_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/agent_run_response.py b/seclai/_generated/models/agent_run_response.py index bef4fd7..f0e41ae 100644 --- a/seclai/_generated/models/agent_run_response.py +++ b/seclai/_generated/models/agent_run_response.py @@ -46,12 +46,16 @@ class AgentRunResponse: output_content_type (None | str | Unset): MIME type of `output` — mirrors the terminal step's `output_content_type`. Consumers interpret `output` differently depending on this value: `application/vnd.seclai.manifest+json` is a multi-asset manifest with shape `{text, attachments: [{storage_key, - mime, name, bytes}]}` — fetch each attachment via `GET /authenticated/storage-blobs/{storage_key}`. - `text/plain` / `text/*` are free-form text. `application/json` is a JSON document. Null on runs that produced - no terminal output or that pre-date this column. + mime, name, bytes}]}` — fetch each attachment via `GET /api/v2/agent-runs/{run_id}/attachments/{attachment_id}`, + where `attachment_id` is the URL-safe base64 of the attachment's `storage_key` (accepts an API key or OAuth + token). `text/plain` / `text/*` are free-form text. `application/json` is a JSON document. Null on runs that + produced no terminal output or that pre-date this column. scan_wait_ms (int | None | Unset): Milliseconds spent waiting for prompt injection scan. steps (list[AgentRunStepResponse] | None | Unset): Step outputs and per-step timing/credits. Only included when requested. + wait_ms (int | None | Unset): Cumulative milliseconds the run was parked on standard-mode wait steps. + Subtracted from active duration in run-detail and duration-stats responses, exactly like hitl_wait_ms. Priority + waits block inline and are not counted here. """ attempts: list[AgentRunAttemptResponse] @@ -71,6 +75,7 @@ class AgentRunResponse: output_content_type: None | str | Unset = UNSET scan_wait_ms: int | None | Unset = UNSET steps: list[AgentRunStepResponse] | None | Unset = UNSET + wait_ms: int | None | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -158,6 +163,12 @@ def to_dict(self) -> dict[str, Any]: else: steps = self.steps + wait_ms: int | None | Unset + if isinstance(self.wait_ms, Unset): + wait_ms = UNSET + else: + wait_ms = self.wait_ms + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -190,6 +201,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["scan_wait_ms"] = scan_wait_ms if steps is not UNSET: field_dict["steps"] = steps + if wait_ms is not UNSET: + field_dict["wait_ms"] = wait_ms return field_dict @@ -342,6 +355,15 @@ def _parse_steps(data: object) -> list[AgentRunStepResponse] | None | Unset: steps = _parse_steps(d.pop("steps", UNSET)) + def _parse_wait_ms(data: object) -> int | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(int | None | Unset, data) + + wait_ms = _parse_wait_ms(d.pop("wait_ms", UNSET)) + agent_run_response = cls( attempts=attempts, credits_=credits_, @@ -360,6 +382,7 @@ def _parse_steps(data: object) -> list[AgentRunStepResponse] | None | Unset: output_content_type=output_content_type, scan_wait_ms=scan_wait_ms, steps=steps, + wait_ms=wait_ms, ) agent_run_response.additional_properties = d diff --git a/seclai/_generated/models/agent_summary_response.py b/seclai/_generated/models/agent_summary_response.py index 4a0f675..ed8789b 100644 --- a/seclai/_generated/models/agent_summary_response.py +++ b/seclai/_generated/models/agent_summary_response.py @@ -28,6 +28,9 @@ class AgentSummaryResponse: name (str): Agent name. updated_at (str): ISO 8601 last-updated timestamp. default_evaluation_tier (None | str | Unset): Default evaluation tier: fast, balanced, or thorough. + disabled (bool | Unset): Whether the agent is paused (disabled). Default: False. + disabled_at (None | str | Unset): ISO 8601 timestamp the agent was paused. + disabled_reason (None | str | Unset): Why the agent is paused: 'manual' or 'email_overload'. evaluation_mode (str | Unset): Evaluation mode: output_expectation, eval_and_retry, or sample_and_flag. Default: 'eval_and_retry'. import_warnings (list[ImportSkipResponse] | None | Unset): One entry per item dropped or substituted during @@ -52,6 +55,9 @@ class AgentSummaryResponse: name: str updated_at: str default_evaluation_tier: None | str | Unset = UNSET + disabled: bool | Unset = False + disabled_at: None | str | Unset = UNSET + disabled_reason: None | str | Unset = UNSET evaluation_mode: str | Unset = "eval_and_retry" import_warnings: list[ImportSkipResponse] | None | Unset = UNSET max_retries: int | Unset = 3 @@ -85,6 +91,20 @@ def to_dict(self) -> dict[str, Any]: else: default_evaluation_tier = self.default_evaluation_tier + disabled = self.disabled + + disabled_at: None | str | Unset + if isinstance(self.disabled_at, Unset): + disabled_at = UNSET + else: + disabled_at = self.disabled_at + + disabled_reason: None | str | Unset + if isinstance(self.disabled_reason, Unset): + disabled_reason = UNSET + else: + disabled_reason = self.disabled_reason + evaluation_mode = self.evaluation_mode import_warnings: list[dict[str, Any]] | None | Unset @@ -147,6 +167,12 @@ def to_dict(self) -> dict[str, Any]: ) if default_evaluation_tier is not UNSET: field_dict["default_evaluation_tier"] = default_evaluation_tier + if disabled is not UNSET: + field_dict["disabled"] = disabled + if disabled_at is not UNSET: + field_dict["disabled_at"] = disabled_at + if disabled_reason is not UNSET: + field_dict["disabled_reason"] = disabled_reason if evaluation_mode is not UNSET: field_dict["evaluation_mode"] = evaluation_mode if import_warnings is not UNSET: @@ -208,6 +234,26 @@ def _parse_default_evaluation_tier(data: object) -> None | str | Unset: d.pop("default_evaluation_tier", UNSET) ) + disabled = d.pop("disabled", UNSET) + + def _parse_disabled_at(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + disabled_at = _parse_disabled_at(d.pop("disabled_at", UNSET)) + + def _parse_disabled_reason(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + disabled_reason = _parse_disabled_reason(d.pop("disabled_reason", UNSET)) + evaluation_mode = d.pop("evaluation_mode", UNSET) def _parse_import_warnings( @@ -308,6 +354,9 @@ def _parse_trigger_type(data: object) -> None | str | Unset: name=name, updated_at=updated_at, default_evaluation_tier=default_evaluation_tier, + disabled=disabled, + disabled_at=disabled_at, + disabled_reason=disabled_reason, evaluation_mode=evaluation_mode, import_warnings=import_warnings, max_retries=max_retries, diff --git a/seclai/_generated/models/block_email_sender_request.py b/seclai/_generated/models/block_email_sender_request.py new file mode 100644 index 0000000..9d5d86b --- /dev/null +++ b/seclai/_generated/models/block_email_sender_request.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="BlockEmailSenderRequest") + + +@_attrs_define +class BlockEmailSenderRequest: + """Add one sender/domain to the account blocklist (shared REST request). + + Attributes: + sender_email (str): + match_type (str | Unset): Default: 'address'. + note (None | str | Unset): + """ + + sender_email: str + match_type: str | Unset = "address" + note: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + sender_email = self.sender_email + + match_type = self.match_type + + note: None | str | Unset + if isinstance(self.note, Unset): + note = UNSET + else: + note = self.note + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "sender_email": sender_email, + } + ) + if match_type is not UNSET: + field_dict["match_type"] = match_type + if note is not UNSET: + field_dict["note"] = note + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + sender_email = d.pop("sender_email") + + match_type = d.pop("match_type", UNSET) + + def _parse_note(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + note = _parse_note(d.pop("note", UNSET)) + + block_email_sender_request = cls( + sender_email=sender_email, + match_type=match_type, + note=note, + ) + + block_email_sender_request.additional_properties = d + return block_email_sender_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/blocked_email_sender_list_response.py b/seclai/_generated/models/blocked_email_sender_list_response.py new file mode 100644 index 0000000..9b009e8 --- /dev/null +++ b/seclai/_generated/models/blocked_email_sender_list_response.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.blocked_email_sender_response import BlockedEmailSenderResponse + + +T = TypeVar("T", bound="BlockedEmailSenderListResponse") + + +@_attrs_define +class BlockedEmailSenderListResponse: + """A page of blocked senders + the account's auto-block mode. + + Attributes: + auto_block_mode (str): + items (list[BlockedEmailSenderResponse]): + total (int): + """ + + auto_block_mode: str + items: list[BlockedEmailSenderResponse] + total: int + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + auto_block_mode = self.auto_block_mode + + items = [] + for items_item_data in self.items: + items_item = items_item_data.to_dict() + items.append(items_item) + + total = self.total + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "auto_block_mode": auto_block_mode, + "items": items, + "total": total, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.blocked_email_sender_response import BlockedEmailSenderResponse + + d = dict(src_dict) + auto_block_mode = d.pop("auto_block_mode") + + items = [] + _items = d.pop("items") + for items_item_data in _items: + items_item = BlockedEmailSenderResponse.from_dict(items_item_data) + + items.append(items_item) + + total = d.pop("total") + + blocked_email_sender_list_response = cls( + auto_block_mode=auto_block_mode, + items=items, + total=total, + ) + + blocked_email_sender_list_response.additional_properties = d + return blocked_email_sender_list_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/blocked_email_sender_response.py b/seclai/_generated/models/blocked_email_sender_response.py new file mode 100644 index 0000000..8fdb67a --- /dev/null +++ b/seclai/_generated/models/blocked_email_sender_response.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="BlockedEmailSenderResponse") + + +@_attrs_define +class BlockedEmailSenderResponse: + """A single blocked inbound email sender. + + Attributes: + created_at (str): + id (UUID): + match_type (str): + note (None | str): + sender_email (str): + source (str): + """ + + created_at: str + id: UUID + match_type: str + note: None | str + sender_email: str + source: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + created_at = self.created_at + + id = str(self.id) + + match_type = self.match_type + + note: None | str + note = self.note + + sender_email = self.sender_email + + source = self.source + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "created_at": created_at, + "id": id, + "match_type": match_type, + "note": note, + "sender_email": sender_email, + "source": source, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + created_at = d.pop("created_at") + + id = UUID(d.pop("id")) + + match_type = d.pop("match_type") + + def _parse_note(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + note = _parse_note(d.pop("note")) + + sender_email = d.pop("sender_email") + + source = d.pop("source") + + blocked_email_sender_response = cls( + created_at=created_at, + id=id, + match_type=match_type, + note=note, + sender_email=sender_email, + source=source, + ) + + blocked_email_sender_response.additional_properties = d + return blocked_email_sender_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/cancel_queued_runs_response.py b/seclai/_generated/models/cancel_queued_runs_response.py new file mode 100644 index 0000000..7b4ddeb --- /dev/null +++ b/seclai/_generated/models/cancel_queued_runs_response.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="CancelQueuedRunsResponse") + + +@_attrs_define +class CancelQueuedRunsResponse: + """ + Attributes: + cancelled (int): + """ + + cancelled: int + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + cancelled = self.cancelled + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "cancelled": cancelled, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + cancelled = d.pop("cancelled") + + cancel_queued_runs_response = cls( + cancelled=cancelled, + ) + + cancel_queued_runs_response.additional_properties = d + return cancel_queued_runs_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/create_agent_request.py b/seclai/_generated/models/create_agent_request.py index ac3f9d7..53a08c1 100644 --- a/seclai/_generated/models/create_agent_request.py +++ b/seclai/_generated/models/create_agent_request.py @@ -36,8 +36,8 @@ class CreateAgentRequest: imported workflow before save. Each key is a source-account UUID (as returned by /agents/preview-import's ``unresolved_refs``); each value is the target-account UUID to substitute. Used to relink knowledge bases, memory banks, source connections, and sub-agents on cross-account imports. - trigger_type (str | Unset): Trigger type: dynamic_input, template_input, schedule, new_content. Default: - 'dynamic_input'. + trigger_type (str | Unset): Trigger type: dynamic_input, template_input, schedule, new_content, email_received. + Default: 'dynamic_input'. """ name: str diff --git a/seclai/_generated/models/create_source_body.py b/seclai/_generated/models/create_source_body.py index 04164f5..41bb672 100644 --- a/seclai/_generated/models/create_source_body.py +++ b/seclai/_generated/models/create_source_body.py @@ -27,6 +27,9 @@ class CreateSourceBody: embedding_model (None | str | Unset): Embedding model override. index_mode (None | SourceIndexMode | Unset): Index mode for custom_index sources: fast_and_cheap (default), balanced, slow_and_thorough, or custom. + media_types (list[str] | None | Unset): Media kinds to extract from indexed content and embed as multi-modal KB + chunks. Subset of ['images', 'video']. Only kinds the source's embedder can index are honored; unsupported + values are dropped. Omit / [] for text-only. polling (None | str | Unset): Polling interval (e.g. hourly, daily). polling_action (None | str | Unset): Polling action. polling_max_items (int | None | Unset): Max items per poll. @@ -42,6 +45,7 @@ class CreateSourceBody: dimensions: int | None | Unset = UNSET embedding_model: None | str | Unset = UNSET index_mode: None | SourceIndexMode | Unset = UNSET + media_types: list[str] | None | Unset = UNSET polling: None | str | Unset = UNSET polling_action: None | str | Unset = UNSET polling_max_items: int | None | Unset = UNSET @@ -92,6 +96,15 @@ def to_dict(self) -> dict[str, Any]: else: index_mode = self.index_mode + media_types: list[str] | None | Unset + if isinstance(self.media_types, Unset): + media_types = UNSET + elif isinstance(self.media_types, list): + media_types = self.media_types + + else: + media_types = self.media_types + polling: None | str | Unset if isinstance(self.polling, Unset): polling = UNSET @@ -142,6 +155,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["embedding_model"] = embedding_model if index_mode is not UNSET: field_dict["index_mode"] = index_mode + if media_types is not UNSET: + field_dict["media_types"] = media_types if polling is not UNSET: field_dict["polling"] = polling if polling_action is not UNSET: @@ -224,6 +239,23 @@ def _parse_index_mode(data: object) -> None | SourceIndexMode | Unset: index_mode = _parse_index_mode(d.pop("index_mode", UNSET)) + def _parse_media_types(data: object) -> list[str] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + media_types_type_0 = cast(list[str], data) + + return media_types_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[str] | None | Unset, data) + + media_types = _parse_media_types(d.pop("media_types", UNSET)) + def _parse_polling(data: object) -> None | str | Unset: if data is None: return data @@ -278,6 +310,7 @@ def _parse_url_id(data: object) -> None | str | Unset: dimensions=dimensions, embedding_model=embedding_model, index_mode=index_mode, + media_types=media_types, polling=polling, polling_action=polling_action, polling_max_items=polling_max_items, diff --git a/seclai/_generated/models/dmarc_failing_source_response.py b/seclai/_generated/models/dmarc_failing_source_response.py new file mode 100644 index 0000000..73d4967 --- /dev/null +++ b/seclai/_generated/models/dmarc_failing_source_response.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="DmarcFailingSourceResponse") + + +@_attrs_define +class DmarcFailingSourceResponse: + """ + Attributes: + failed_count (int): + source_ip (str): + header_from (None | str | Unset): + """ + + failed_count: int + source_ip: str + header_from: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + failed_count = self.failed_count + + source_ip = self.source_ip + + header_from: None | str | Unset + if isinstance(self.header_from, Unset): + header_from = UNSET + else: + header_from = self.header_from + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "failed_count": failed_count, + "source_ip": source_ip, + } + ) + if header_from is not UNSET: + field_dict["header_from"] = header_from + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + failed_count = d.pop("failed_count") + + source_ip = d.pop("source_ip") + + def _parse_header_from(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + header_from = _parse_header_from(d.pop("header_from", UNSET)) + + dmarc_failing_source_response = cls( + failed_count=failed_count, + source_ip=source_ip, + header_from=header_from, + ) + + dmarc_failing_source_response.additional_properties = d + return dmarc_failing_source_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/dmarc_summary_response.py b/seclai/_generated/models/dmarc_summary_response.py new file mode 100644 index 0000000..828f819 --- /dev/null +++ b/seclai/_generated/models/dmarc_summary_response.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.dmarc_failing_source_response import DmarcFailingSourceResponse + from ..models.dmarc_summary_response_dispositions import ( + DmarcSummaryResponseDispositions, + ) + + +T = TypeVar("T", bound="DmarcSummaryResponse") + + +@_attrs_define +class DmarcSummaryResponse: + """ + Attributes: + failed_messages (int): + passed_messages (int): + report_count (int): + total_messages (int): + window_days (int): + dispositions (DmarcSummaryResponseDispositions | Unset): + monitored (bool | Unset): Default: True. + pass_rate (float | None | Unset): + top_failing_sources (list[DmarcFailingSourceResponse] | Unset): + """ + + failed_messages: int + passed_messages: int + report_count: int + total_messages: int + window_days: int + dispositions: DmarcSummaryResponseDispositions | Unset = UNSET + monitored: bool | Unset = True + pass_rate: float | None | Unset = UNSET + top_failing_sources: list[DmarcFailingSourceResponse] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + failed_messages = self.failed_messages + + passed_messages = self.passed_messages + + report_count = self.report_count + + total_messages = self.total_messages + + window_days = self.window_days + + dispositions: dict[str, Any] | Unset = UNSET + if not isinstance(self.dispositions, Unset): + dispositions = self.dispositions.to_dict() + + monitored = self.monitored + + pass_rate: float | None | Unset + if isinstance(self.pass_rate, Unset): + pass_rate = UNSET + else: + pass_rate = self.pass_rate + + top_failing_sources: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.top_failing_sources, Unset): + top_failing_sources = [] + for top_failing_sources_item_data in self.top_failing_sources: + top_failing_sources_item = top_failing_sources_item_data.to_dict() + top_failing_sources.append(top_failing_sources_item) + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "failed_messages": failed_messages, + "passed_messages": passed_messages, + "report_count": report_count, + "total_messages": total_messages, + "window_days": window_days, + } + ) + if dispositions is not UNSET: + field_dict["dispositions"] = dispositions + if monitored is not UNSET: + field_dict["monitored"] = monitored + if pass_rate is not UNSET: + field_dict["pass_rate"] = pass_rate + if top_failing_sources is not UNSET: + field_dict["top_failing_sources"] = top_failing_sources + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.dmarc_failing_source_response import DmarcFailingSourceResponse + from ..models.dmarc_summary_response_dispositions import ( + DmarcSummaryResponseDispositions, + ) + + d = dict(src_dict) + failed_messages = d.pop("failed_messages") + + passed_messages = d.pop("passed_messages") + + report_count = d.pop("report_count") + + total_messages = d.pop("total_messages") + + window_days = d.pop("window_days") + + _dispositions = d.pop("dispositions", UNSET) + dispositions: DmarcSummaryResponseDispositions | Unset + if isinstance(_dispositions, Unset): + dispositions = UNSET + else: + dispositions = DmarcSummaryResponseDispositions.from_dict(_dispositions) + + monitored = d.pop("monitored", UNSET) + + def _parse_pass_rate(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + pass_rate = _parse_pass_rate(d.pop("pass_rate", UNSET)) + + _top_failing_sources = d.pop("top_failing_sources", UNSET) + top_failing_sources: list[DmarcFailingSourceResponse] | Unset = UNSET + if _top_failing_sources is not UNSET: + top_failing_sources = [] + for top_failing_sources_item_data in _top_failing_sources: + top_failing_sources_item = DmarcFailingSourceResponse.from_dict( + top_failing_sources_item_data + ) + + top_failing_sources.append(top_failing_sources_item) + + dmarc_summary_response = cls( + failed_messages=failed_messages, + passed_messages=passed_messages, + report_count=report_count, + total_messages=total_messages, + window_days=window_days, + dispositions=dispositions, + monitored=monitored, + pass_rate=pass_rate, + top_failing_sources=top_failing_sources, + ) + + dmarc_summary_response.additional_properties = d + return dmarc_summary_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/dmarc_summary_response_dispositions.py b/seclai/_generated/models/dmarc_summary_response_dispositions.py new file mode 100644 index 0000000..9468cd6 --- /dev/null +++ b/seclai/_generated/models/dmarc_summary_response_dispositions.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="DmarcSummaryResponseDispositions") + + +@_attrs_define +class DmarcSummaryResponseDispositions: + """ """ + + additional_properties: dict[str, int] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + dmarc_summary_response_dispositions = cls() + + dmarc_summary_response_dispositions.additional_properties = d + return dmarc_summary_response_dispositions + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> int: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: int) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/dns_provider_response.py b/seclai/_generated/models/dns_provider_response.py new file mode 100644 index 0000000..dc98ae2 --- /dev/null +++ b/seclai/_generated/models/dns_provider_response.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="DnsProviderResponse") + + +@_attrs_define +class DnsProviderResponse: + """ + Attributes: + key (str): + name (str): + dashboard_url (None | str | Unset): + mx_priority_separate (bool | Unset): Default: True. + tips (list[str] | Unset): + txt_quotes (str | Unset): Default: 'strip'. + """ + + key: str + name: str + dashboard_url: None | str | Unset = UNSET + mx_priority_separate: bool | Unset = True + tips: list[str] | Unset = UNSET + txt_quotes: str | Unset = "strip" + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + key = self.key + + name = self.name + + dashboard_url: None | str | Unset + if isinstance(self.dashboard_url, Unset): + dashboard_url = UNSET + else: + dashboard_url = self.dashboard_url + + mx_priority_separate = self.mx_priority_separate + + tips: list[str] | Unset = UNSET + if not isinstance(self.tips, Unset): + tips = self.tips + + txt_quotes = self.txt_quotes + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "key": key, + "name": name, + } + ) + if dashboard_url is not UNSET: + field_dict["dashboard_url"] = dashboard_url + if mx_priority_separate is not UNSET: + field_dict["mx_priority_separate"] = mx_priority_separate + if tips is not UNSET: + field_dict["tips"] = tips + if txt_quotes is not UNSET: + field_dict["txt_quotes"] = txt_quotes + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + key = d.pop("key") + + name = d.pop("name") + + def _parse_dashboard_url(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + dashboard_url = _parse_dashboard_url(d.pop("dashboard_url", UNSET)) + + mx_priority_separate = d.pop("mx_priority_separate", UNSET) + + tips = cast(list[str], d.pop("tips", UNSET)) + + txt_quotes = d.pop("txt_quotes", UNSET) + + dns_provider_response = cls( + key=key, + name=name, + dashboard_url=dashboard_url, + mx_priority_separate=mx_priority_separate, + tips=tips, + txt_quotes=txt_quotes, + ) + + dns_provider_response.additional_properties = d + return dns_provider_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/dns_record_response.py b/seclai/_generated/models/dns_record_response.py new file mode 100644 index 0000000..ae24425 --- /dev/null +++ b/seclai/_generated/models/dns_record_response.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="DnsRecordResponse") + + +@_attrs_define +class DnsRecordResponse: + """ + Attributes: + key (str): + name (str): + ok (bool): + relative_name (str): + type_ (str): + value (str): + detail (None | str | Unset): + mx_host (None | str | Unset): + mx_priority (int | None | Unset): + """ + + key: str + name: str + ok: bool + relative_name: str + type_: str + value: str + detail: None | str | Unset = UNSET + mx_host: None | str | Unset = UNSET + mx_priority: int | None | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + key = self.key + + name = self.name + + ok = self.ok + + relative_name = self.relative_name + + type_ = self.type_ + + value = self.value + + detail: None | str | Unset + if isinstance(self.detail, Unset): + detail = UNSET + else: + detail = self.detail + + mx_host: None | str | Unset + if isinstance(self.mx_host, Unset): + mx_host = UNSET + else: + mx_host = self.mx_host + + mx_priority: int | None | Unset + if isinstance(self.mx_priority, Unset): + mx_priority = UNSET + else: + mx_priority = self.mx_priority + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "key": key, + "name": name, + "ok": ok, + "relative_name": relative_name, + "type": type_, + "value": value, + } + ) + if detail is not UNSET: + field_dict["detail"] = detail + if mx_host is not UNSET: + field_dict["mx_host"] = mx_host + if mx_priority is not UNSET: + field_dict["mx_priority"] = mx_priority + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + key = d.pop("key") + + name = d.pop("name") + + ok = d.pop("ok") + + relative_name = d.pop("relative_name") + + type_ = d.pop("type") + + value = d.pop("value") + + def _parse_detail(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + detail = _parse_detail(d.pop("detail", UNSET)) + + def _parse_mx_host(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + mx_host = _parse_mx_host(d.pop("mx_host", UNSET)) + + def _parse_mx_priority(data: object) -> int | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(int | None | Unset, data) + + mx_priority = _parse_mx_priority(d.pop("mx_priority", UNSET)) + + dns_record_response = cls( + key=key, + name=name, + ok=ok, + relative_name=relative_name, + type_=type_, + value=value, + detail=detail, + mx_host=mx_host, + mx_priority=mx_priority, + ) + + dns_record_response.additional_properties = d + return dns_record_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/docs_search_api_docs_search_get_mode.py b/seclai/_generated/models/docs_search_api_docs_search_get_mode.py new file mode 100644 index 0000000..8b240e5 --- /dev/null +++ b/seclai/_generated/models/docs_search_api_docs_search_get_mode.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class DocsSearchApiDocsSearchGetMode(str, Enum): + KEYWORD = "keyword" + SEMANTIC = "semantic" + + def __str__(self) -> str: + return str(self.value) diff --git a/seclai/_generated/models/docs_search_api_docs_search_get_response_docs_search_api_docs_search_get.py b/seclai/_generated/models/docs_search_api_docs_search_get_response_docs_search_api_docs_search_get.py new file mode 100644 index 0000000..9c792ff --- /dev/null +++ b/seclai/_generated/models/docs_search_api_docs_search_get_response_docs_search_api_docs_search_get.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet") + + +@_attrs_define +class DocsSearchApiDocsSearchGetResponseDocsSearchApiDocsSearchGet: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + docs_search_api_docs_search_get_response_docs_search_api_docs_search_get = cls() + + docs_search_api_docs_search_get_response_docs_search_api_docs_search_get.additional_properties = ( + d + ) + return docs_search_api_docs_search_get_response_docs_search_api_docs_search_get + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/email_domain_response.py b/seclai/_generated/models/email_domain_response.py new file mode 100644 index 0000000..aa54410 --- /dev/null +++ b/seclai/_generated/models/email_domain_response.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.dns_provider_response import DnsProviderResponse + from ..models.dns_record_response import DnsRecordResponse + + +T = TypeVar("T", bound="EmailDomainResponse") + + +@_attrs_define +class EmailDomainResponse: + """ + Attributes: + domain (str): + id (UUID): + is_primary (bool): + kind (str): + status (str): + delegated (bool | Unset): Default: False. + dns_records (list[DnsRecordResponse] | Unset): + error_message (None | str | Unset): + last_checked_at (datetime.datetime | None | Unset): + provider (DnsProviderResponse | None | Unset): + regressing (bool | Unset): Default: False. + verified (bool | Unset): Default: False. + verified_at (datetime.datetime | None | Unset): + zone_apex (str | Unset): Default: ''. + """ + + domain: str + id: UUID + is_primary: bool + kind: str + status: str + delegated: bool | Unset = False + dns_records: list[DnsRecordResponse] | Unset = UNSET + error_message: None | str | Unset = UNSET + last_checked_at: datetime.datetime | None | Unset = UNSET + provider: DnsProviderResponse | None | Unset = UNSET + regressing: bool | Unset = False + verified: bool | Unset = False + verified_at: datetime.datetime | None | Unset = UNSET + zone_apex: str | Unset = "" + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.dns_provider_response import DnsProviderResponse + + domain = self.domain + + id = str(self.id) + + is_primary = self.is_primary + + kind = self.kind + + status = self.status + + delegated = self.delegated + + dns_records: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.dns_records, Unset): + dns_records = [] + for dns_records_item_data in self.dns_records: + dns_records_item = dns_records_item_data.to_dict() + dns_records.append(dns_records_item) + + error_message: None | str | Unset + if isinstance(self.error_message, Unset): + error_message = UNSET + else: + error_message = self.error_message + + last_checked_at: None | str | Unset + if isinstance(self.last_checked_at, Unset): + last_checked_at = UNSET + elif isinstance(self.last_checked_at, datetime.datetime): + last_checked_at = self.last_checked_at.isoformat() + else: + last_checked_at = self.last_checked_at + + provider: dict[str, Any] | None | Unset + if isinstance(self.provider, Unset): + provider = UNSET + elif isinstance(self.provider, DnsProviderResponse): + provider = self.provider.to_dict() + else: + provider = self.provider + + regressing = self.regressing + + verified = self.verified + + verified_at: None | str | Unset + if isinstance(self.verified_at, Unset): + verified_at = UNSET + elif isinstance(self.verified_at, datetime.datetime): + verified_at = self.verified_at.isoformat() + else: + verified_at = self.verified_at + + zone_apex = self.zone_apex + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "domain": domain, + "id": id, + "is_primary": is_primary, + "kind": kind, + "status": status, + } + ) + if delegated is not UNSET: + field_dict["delegated"] = delegated + if dns_records is not UNSET: + field_dict["dns_records"] = dns_records + if error_message is not UNSET: + field_dict["error_message"] = error_message + if last_checked_at is not UNSET: + field_dict["last_checked_at"] = last_checked_at + if provider is not UNSET: + field_dict["provider"] = provider + if regressing is not UNSET: + field_dict["regressing"] = regressing + if verified is not UNSET: + field_dict["verified"] = verified + if verified_at is not UNSET: + field_dict["verified_at"] = verified_at + if zone_apex is not UNSET: + field_dict["zone_apex"] = zone_apex + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.dns_provider_response import DnsProviderResponse + from ..models.dns_record_response import DnsRecordResponse + + d = dict(src_dict) + domain = d.pop("domain") + + id = UUID(d.pop("id")) + + is_primary = d.pop("is_primary") + + kind = d.pop("kind") + + status = d.pop("status") + + delegated = d.pop("delegated", UNSET) + + _dns_records = d.pop("dns_records", UNSET) + dns_records: list[DnsRecordResponse] | Unset = UNSET + if _dns_records is not UNSET: + dns_records = [] + for dns_records_item_data in _dns_records: + dns_records_item = DnsRecordResponse.from_dict(dns_records_item_data) + + dns_records.append(dns_records_item) + + def _parse_error_message(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + error_message = _parse_error_message(d.pop("error_message", UNSET)) + + def _parse_last_checked_at(data: object) -> datetime.datetime | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + last_checked_at_type_0 = isoparse(data) + + return last_checked_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None | Unset, data) + + last_checked_at = _parse_last_checked_at(d.pop("last_checked_at", UNSET)) + + def _parse_provider(data: object) -> DnsProviderResponse | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + provider_type_0 = DnsProviderResponse.from_dict(data) + + return provider_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(DnsProviderResponse | None | Unset, data) + + provider = _parse_provider(d.pop("provider", UNSET)) + + regressing = d.pop("regressing", UNSET) + + verified = d.pop("verified", UNSET) + + def _parse_verified_at(data: object) -> datetime.datetime | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + verified_at_type_0 = isoparse(data) + + return verified_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None | Unset, data) + + verified_at = _parse_verified_at(d.pop("verified_at", UNSET)) + + zone_apex = d.pop("zone_apex", UNSET) + + email_domain_response = cls( + domain=domain, + id=id, + is_primary=is_primary, + kind=kind, + status=status, + delegated=delegated, + dns_records=dns_records, + error_message=error_message, + last_checked_at=last_checked_at, + provider=provider, + regressing=regressing, + verified=verified, + verified_at=verified_at, + zone_apex=zone_apex, + ) + + email_domain_response.additional_properties = d + return email_domain_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/email_domains_list_response.py b/seclai/_generated/models/email_domains_list_response.py new file mode 100644 index 0000000..2f1a8d6 --- /dev/null +++ b/seclai/_generated/models/email_domains_list_response.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.email_domain_response import EmailDomainResponse + + +T = TypeVar("T", bound="EmailDomainsListResponse") + + +@_attrs_define +class EmailDomainsListResponse: + """ + Attributes: + can_add_custom (bool | Unset): Default: False. + can_add_vanity (bool | Unset): Default: False. + custom_plan_names (list[str] | Unset): + domains (list[EmailDomainResponse] | Unset): + has_custom (bool | Unset): Default: False. + has_vanity (bool | Unset): Default: False. + vanity_plan_names (list[str] | Unset): + """ + + can_add_custom: bool | Unset = False + can_add_vanity: bool | Unset = False + custom_plan_names: list[str] | Unset = UNSET + domains: list[EmailDomainResponse] | Unset = UNSET + has_custom: bool | Unset = False + has_vanity: bool | Unset = False + vanity_plan_names: list[str] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + can_add_custom = self.can_add_custom + + can_add_vanity = self.can_add_vanity + + custom_plan_names: list[str] | Unset = UNSET + if not isinstance(self.custom_plan_names, Unset): + custom_plan_names = self.custom_plan_names + + domains: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.domains, Unset): + domains = [] + for domains_item_data in self.domains: + domains_item = domains_item_data.to_dict() + domains.append(domains_item) + + has_custom = self.has_custom + + has_vanity = self.has_vanity + + vanity_plan_names: list[str] | Unset = UNSET + if not isinstance(self.vanity_plan_names, Unset): + vanity_plan_names = self.vanity_plan_names + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if can_add_custom is not UNSET: + field_dict["can_add_custom"] = can_add_custom + if can_add_vanity is not UNSET: + field_dict["can_add_vanity"] = can_add_vanity + if custom_plan_names is not UNSET: + field_dict["custom_plan_names"] = custom_plan_names + if domains is not UNSET: + field_dict["domains"] = domains + if has_custom is not UNSET: + field_dict["has_custom"] = has_custom + if has_vanity is not UNSET: + field_dict["has_vanity"] = has_vanity + if vanity_plan_names is not UNSET: + field_dict["vanity_plan_names"] = vanity_plan_names + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.email_domain_response import EmailDomainResponse + + d = dict(src_dict) + can_add_custom = d.pop("can_add_custom", UNSET) + + can_add_vanity = d.pop("can_add_vanity", UNSET) + + custom_plan_names = cast(list[str], d.pop("custom_plan_names", UNSET)) + + _domains = d.pop("domains", UNSET) + domains: list[EmailDomainResponse] | Unset = UNSET + if _domains is not UNSET: + domains = [] + for domains_item_data in _domains: + domains_item = EmailDomainResponse.from_dict(domains_item_data) + + domains.append(domains_item) + + has_custom = d.pop("has_custom", UNSET) + + has_vanity = d.pop("has_vanity", UNSET) + + vanity_plan_names = cast(list[str], d.pop("vanity_plan_names", UNSET)) + + email_domains_list_response = cls( + can_add_custom=can_add_custom, + can_add_vanity=can_add_vanity, + custom_plan_names=custom_plan_names, + domains=domains, + has_custom=has_custom, + has_vanity=has_vanity, + vanity_plan_names=vanity_plan_names, + ) + + email_domains_list_response.additional_properties = d + return email_domains_list_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/email_trigger_config_response.py b/seclai/_generated/models/email_trigger_config_response.py new file mode 100644 index 0000000..b2996e6 --- /dev/null +++ b/seclai/_generated/models/email_trigger_config_response.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="EmailTriggerConfigResponse") + + +@_attrs_define +class EmailTriggerConfigResponse: + """An EMAIL_RECEIVED trigger's resolved address(es) and config. + + Attributes: + agent_id (UUID): + trigger_id (UUID): + trigger_type (str): + email_addresses (list[str] | Unset): + email_alias (None | str | Unset): + email_allowed_senders (list[str] | None | Unset): + email_ignore_auto_generated (bool | Unset): Default: True. + email_queue_on_quota (bool | Unset): Default: False. + email_require_sender_auth (bool | Unset): Default: True. + """ + + agent_id: UUID + trigger_id: UUID + trigger_type: str + email_addresses: list[str] | Unset = UNSET + email_alias: None | str | Unset = UNSET + email_allowed_senders: list[str] | None | Unset = UNSET + email_ignore_auto_generated: bool | Unset = True + email_queue_on_quota: bool | Unset = False + email_require_sender_auth: bool | Unset = True + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + agent_id = str(self.agent_id) + + trigger_id = str(self.trigger_id) + + trigger_type = self.trigger_type + + email_addresses: list[str] | Unset = UNSET + if not isinstance(self.email_addresses, Unset): + email_addresses = self.email_addresses + + email_alias: None | str | Unset + if isinstance(self.email_alias, Unset): + email_alias = UNSET + else: + email_alias = self.email_alias + + email_allowed_senders: list[str] | None | Unset + if isinstance(self.email_allowed_senders, Unset): + email_allowed_senders = UNSET + elif isinstance(self.email_allowed_senders, list): + email_allowed_senders = self.email_allowed_senders + + else: + email_allowed_senders = self.email_allowed_senders + + email_ignore_auto_generated = self.email_ignore_auto_generated + + email_queue_on_quota = self.email_queue_on_quota + + email_require_sender_auth = self.email_require_sender_auth + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "agent_id": agent_id, + "trigger_id": trigger_id, + "trigger_type": trigger_type, + } + ) + if email_addresses is not UNSET: + field_dict["email_addresses"] = email_addresses + if email_alias is not UNSET: + field_dict["email_alias"] = email_alias + if email_allowed_senders is not UNSET: + field_dict["email_allowed_senders"] = email_allowed_senders + if email_ignore_auto_generated is not UNSET: + field_dict["email_ignore_auto_generated"] = email_ignore_auto_generated + if email_queue_on_quota is not UNSET: + field_dict["email_queue_on_quota"] = email_queue_on_quota + if email_require_sender_auth is not UNSET: + field_dict["email_require_sender_auth"] = email_require_sender_auth + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + agent_id = UUID(d.pop("agent_id")) + + trigger_id = UUID(d.pop("trigger_id")) + + trigger_type = d.pop("trigger_type") + + email_addresses = cast(list[str], d.pop("email_addresses", UNSET)) + + def _parse_email_alias(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + email_alias = _parse_email_alias(d.pop("email_alias", UNSET)) + + def _parse_email_allowed_senders(data: object) -> list[str] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + email_allowed_senders_type_0 = cast(list[str], data) + + return email_allowed_senders_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[str] | None | Unset, data) + + email_allowed_senders = _parse_email_allowed_senders( + d.pop("email_allowed_senders", UNSET) + ) + + email_ignore_auto_generated = d.pop("email_ignore_auto_generated", UNSET) + + email_queue_on_quota = d.pop("email_queue_on_quota", UNSET) + + email_require_sender_auth = d.pop("email_require_sender_auth", UNSET) + + email_trigger_config_response = cls( + agent_id=agent_id, + trigger_id=trigger_id, + trigger_type=trigger_type, + email_addresses=email_addresses, + email_alias=email_alias, + email_allowed_senders=email_allowed_senders, + email_ignore_auto_generated=email_ignore_auto_generated, + email_queue_on_quota=email_queue_on_quota, + email_require_sender_auth=email_require_sender_auth, + ) + + email_trigger_config_response.additional_properties = d + return email_trigger_config_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/executed_action_response.py b/seclai/_generated/models/executed_action_response.py index 7c1ba69..5feb976 100644 --- a/seclai/_generated/models/executed_action_response.py +++ b/seclai/_generated/models/executed_action_response.py @@ -19,6 +19,8 @@ class ExecutedActionResponse: action_type (str): Type of the executed action. description (str): Human-readable description. error (None | str | Unset): Error message if failed. + needs_clarification (bool | Unset): True when the action paused for a clarifying question rather than failing. + The created resource is kept and the question is in ``description``/``error``. Default: False. resource_id (None | str | Unset): ID of the affected resource. resource_type (None | str | Unset): Type of the affected resource. success (bool | Unset): Whether the action succeeded. Default: True. @@ -27,6 +29,7 @@ class ExecutedActionResponse: action_type: str description: str error: None | str | Unset = UNSET + needs_clarification: bool | Unset = False resource_id: None | str | Unset = UNSET resource_type: None | str | Unset = UNSET success: bool | Unset = True @@ -43,6 +46,8 @@ def to_dict(self) -> dict[str, Any]: else: error = self.error + needs_clarification = self.needs_clarification + resource_id: None | str | Unset if isinstance(self.resource_id, Unset): resource_id = UNSET @@ -67,6 +72,8 @@ def to_dict(self) -> dict[str, Any]: ) if error is not UNSET: field_dict["error"] = error + if needs_clarification is not UNSET: + field_dict["needs_clarification"] = needs_clarification if resource_id is not UNSET: field_dict["resource_id"] = resource_id if resource_type is not UNSET: @@ -92,6 +99,8 @@ def _parse_error(data: object) -> None | str | Unset: error = _parse_error(d.pop("error", UNSET)) + needs_clarification = d.pop("needs_clarification", UNSET) + def _parse_resource_id(data: object) -> None | str | Unset: if data is None: return data @@ -116,6 +125,7 @@ def _parse_resource_type(data: object) -> None | str | Unset: action_type=action_type, description=description, error=error, + needs_clarification=needs_clarification, resource_id=resource_id, resource_type=resource_type, success=success, diff --git a/seclai/_generated/models/generate_agent_steps_response.py b/seclai/_generated/models/generate_agent_steps_response.py index 393aa54..3989e73 100644 --- a/seclai/_generated/models/generate_agent_steps_response.py +++ b/seclai/_generated/models/generate_agent_steps_response.py @@ -33,6 +33,10 @@ class GenerateAgentStepsResponse: if any. example_prompts (list[ExamplePrompt] | Unset): Example natural-language prompts that demonstrate the capabilities of this AI assistant for the given mode. + intent_assessment (None | str | Unset): How the assistant interpreted the request: 'clear' when steps were + generated, or an ask-path value (e.g. 'ambiguous_output', 'cannot_build') when it returned no steps and put a + clarifying question or blocker in `note`. Mirrors the MCP surface so callers can distinguish a clarification + pause from a hard failure. """ conversation_id: str @@ -41,6 +45,7 @@ class GenerateAgentStepsResponse: success: bool agent_config: GenerateAgentStepsResponseAgentConfigType0 | None | Unset = UNSET example_prompts: list[ExamplePrompt] | Unset = UNSET + intent_assessment: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -74,6 +79,12 @@ def to_dict(self) -> dict[str, Any]: example_prompts_item = example_prompts_item_data.to_dict() example_prompts.append(example_prompts_item) + intent_assessment: None | str | Unset + if isinstance(self.intent_assessment, Unset): + intent_assessment = UNSET + else: + intent_assessment = self.intent_assessment + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -88,6 +99,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["agent_config"] = agent_config if example_prompts is not UNSET: field_dict["example_prompts"] = example_prompts + if intent_assessment is not UNSET: + field_dict["intent_assessment"] = intent_assessment return field_dict @@ -147,6 +160,15 @@ def _parse_agent_config( example_prompts.append(example_prompts_item) + def _parse_intent_assessment(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + intent_assessment = _parse_intent_assessment(d.pop("intent_assessment", UNSET)) + generate_agent_steps_response = cls( conversation_id=conversation_id, note=note, @@ -154,6 +176,7 @@ def _parse_agent_config( success=success, agent_config=agent_config, example_prompts=example_prompts, + intent_assessment=intent_assessment, ) generate_agent_steps_response.additional_properties = d diff --git a/seclai/_generated/models/get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get.py b/seclai/_generated/models/get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get.py new file mode 100644 index 0000000..75b9537 --- /dev/null +++ b/seclai/_generated/models/get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar( + "T", + bound="GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet", +) + + +@_attrs_define +class GetGenerationTiersApiModelsGenerationTiersGetResponseGetGenerationTiersApiModelsGenerationTiersGet: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get = ( + cls() + ) + + get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get.additional_properties = ( + d + ) + return get_generation_tiers_api_models_generation_tiers_get_response_get_generation_tiers_api_models_generation_tiers_get + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/inbound_email_rejection_response.py b/seclai/_generated/models/inbound_email_rejection_response.py new file mode 100644 index 0000000..0cba533 --- /dev/null +++ b/seclai/_generated/models/inbound_email_rejection_response.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="InboundEmailRejectionResponse") + + +@_attrs_define +class InboundEmailRejectionResponse: + """An inbound email that was discarded without running an agent. + + Attributes: + agent_id (None | UUID): + created_at (str): + id (UUID): + message_id (None | str): + reason (str): + recipient (str): + sender (str): + sender_ip (None | str): + subject (None | str): + """ + + agent_id: None | UUID + created_at: str + id: UUID + message_id: None | str + reason: str + recipient: str + sender: str + sender_ip: None | str + subject: None | str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + agent_id: None | str + if isinstance(self.agent_id, UUID): + agent_id = str(self.agent_id) + else: + agent_id = self.agent_id + + created_at = self.created_at + + id = str(self.id) + + message_id: None | str + message_id = self.message_id + + reason = self.reason + + recipient = self.recipient + + sender = self.sender + + sender_ip: None | str + sender_ip = self.sender_ip + + subject: None | str + subject = self.subject + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "agent_id": agent_id, + "created_at": created_at, + "id": id, + "message_id": message_id, + "reason": reason, + "recipient": recipient, + "sender": sender, + "sender_ip": sender_ip, + "subject": subject, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + + def _parse_agent_id(data: object) -> None | UUID: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + agent_id_type_0 = UUID(data) + + return agent_id_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | UUID, data) + + agent_id = _parse_agent_id(d.pop("agent_id")) + + created_at = d.pop("created_at") + + id = UUID(d.pop("id")) + + def _parse_message_id(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + message_id = _parse_message_id(d.pop("message_id")) + + reason = d.pop("reason") + + recipient = d.pop("recipient") + + sender = d.pop("sender") + + def _parse_sender_ip(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + sender_ip = _parse_sender_ip(d.pop("sender_ip")) + + def _parse_subject(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + subject = _parse_subject(d.pop("subject")) + + inbound_email_rejection_response = cls( + agent_id=agent_id, + created_at=created_at, + id=id, + message_id=message_id, + reason=reason, + recipient=recipient, + sender=sender, + sender_ip=sender_ip, + subject=subject, + ) + + inbound_email_rejection_response.additional_properties = d + return inbound_email_rejection_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/inbound_email_status_response.py b/seclai/_generated/models/inbound_email_status_response.py new file mode 100644 index 0000000..98f25df --- /dev/null +++ b/seclai/_generated/models/inbound_email_status_response.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="InboundEmailStatusResponse") + + +@_attrs_define +class InboundEmailStatusResponse: + """ + Attributes: + paused (bool): + queued_backlog (int): + """ + + paused: bool + queued_backlog: int + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + paused = self.paused + + queued_backlog = self.queued_backlog + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "paused": paused, + "queued_backlog": queued_backlog, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + paused = d.pop("paused") + + queued_backlog = d.pop("queued_backlog") + + inbound_email_status_response = cls( + paused=paused, + queued_backlog=queued_backlog, + ) + + inbound_email_status_response.additional_properties = d + return inbound_email_status_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/pending_processing_completed_failed_status.py b/seclai/_generated/models/pending_processing_completed_failed_status.py index fa37fd4..690e4f0 100644 --- a/seclai/_generated/models/pending_processing_completed_failed_status.py +++ b/seclai/_generated/models/pending_processing_completed_failed_status.py @@ -6,7 +6,9 @@ class PendingProcessingCompletedFailedStatus(str, Enum): FAILED = "failed" PENDING = "pending" PROCESSING = "processing" + QUEUED = "queued" WAITING_HUMAN = "waiting_human" + WAITING_SCHEDULED = "waiting_scheduled" def __str__(self) -> str: return str(self.value) diff --git a/seclai/_generated/models/prompt_model_response.py b/seclai/_generated/models/prompt_model_response.py index 9532c01..62954d0 100644 --- a/seclai/_generated/models/prompt_model_response.py +++ b/seclai/_generated/models/prompt_model_response.py @@ -12,6 +12,9 @@ if TYPE_CHECKING: from ..models.modality_rate_response import ModalityRateResponse + from ..models.prompt_model_response_generation_params_type_0 import ( + PromptModelResponseGenerationParamsType0, + ) from ..models.prompt_model_response_payload_schema_type_0 import ( PromptModelResponsePayloadSchemaType0, ) @@ -40,6 +43,20 @@ class PromptModelResponse: deprecated_at (datetime.datetime | None | Unset): family (None | str | Unset): family_generation (float | None | Unset): + generation_credits_per_unit (float | None | Unset): Per-unit credit cost for a dedicated media-generation model, + in the unit named by ``generation_params.pricing_unit`` (per image / per second / per character / per output + token). Multiply by the produced unit count (images, seconds, characters) for the run cost. None for token- + billed (non-generation) models. + generation_params (None | PromptModelResponseGenerationParamsType0 | Unset): Media-generation descriptor + (modality, pricing_unit, and modality-specific constraints). NULL for text LLMs; present for image/audio/video + generation models. See schemas.generation_params. + generation_unit_label (None | str | Unset): Human suffix for the per-unit generation rate (e.g. ``/image``, + ``/second``, ``/1k chars``, ``/1k tokens``) — single-sourced from the pricing unit so clients render cost + without re-deriving the mapping. None for non-generation models. Char/token rates are shown per 1,000 (the ``/1k + …`` suffix), so scale ``generation_credits_per_unit`` accordingly for those units. + image_generation_tool_credits_per_image (float | None | Unset): Per-image credit cost of using the built-in + image_generation tool (it runs gpt-image-1). Set only for models that actually support the tool (tool-use + capable); None otherwise. input_1h_cache_write_credits_per_1000_tokens (float | None | Unset): input_5m_cache_write_credits_per_1000_tokens (float | None | Unset): input_cache_hit_credits_per_1000_tokens (float | None | Unset): @@ -85,6 +102,10 @@ class PromptModelResponse: deprecated_at: datetime.datetime | None | Unset = UNSET family: None | str | Unset = UNSET family_generation: float | None | Unset = UNSET + generation_credits_per_unit: float | None | Unset = UNSET + generation_params: None | PromptModelResponseGenerationParamsType0 | Unset = UNSET + generation_unit_label: None | str | Unset = UNSET + image_generation_tool_credits_per_image: float | None | Unset = UNSET input_1h_cache_write_credits_per_1000_tokens: float | None | Unset = UNSET input_5m_cache_write_credits_per_1000_tokens: float | None | Unset = UNSET input_cache_hit_credits_per_1000_tokens: float | None | Unset = UNSET @@ -116,6 +137,9 @@ class PromptModelResponse: additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + from ..models.prompt_model_response_generation_params_type_0 import ( + PromptModelResponseGenerationParamsType0, + ) from ..models.prompt_model_response_payload_schema_type_0 import ( PromptModelResponsePayloadSchemaType0, ) @@ -160,6 +184,36 @@ def to_dict(self) -> dict[str, Any]: else: family_generation = self.family_generation + generation_credits_per_unit: float | None | Unset + if isinstance(self.generation_credits_per_unit, Unset): + generation_credits_per_unit = UNSET + else: + generation_credits_per_unit = self.generation_credits_per_unit + + generation_params: dict[str, Any] | None | Unset + if isinstance(self.generation_params, Unset): + generation_params = UNSET + elif isinstance( + self.generation_params, PromptModelResponseGenerationParamsType0 + ): + generation_params = self.generation_params.to_dict() + else: + generation_params = self.generation_params + + generation_unit_label: None | str | Unset + if isinstance(self.generation_unit_label, Unset): + generation_unit_label = UNSET + else: + generation_unit_label = self.generation_unit_label + + image_generation_tool_credits_per_image: float | None | Unset + if isinstance(self.image_generation_tool_credits_per_image, Unset): + image_generation_tool_credits_per_image = UNSET + else: + image_generation_tool_credits_per_image = ( + self.image_generation_tool_credits_per_image + ) + input_1h_cache_write_credits_per_1000_tokens: float | None | Unset if isinstance(self.input_1h_cache_write_credits_per_1000_tokens, Unset): input_1h_cache_write_credits_per_1000_tokens = UNSET @@ -354,6 +408,16 @@ def to_dict(self) -> dict[str, Any]: field_dict["family"] = family if family_generation is not UNSET: field_dict["family_generation"] = family_generation + if generation_credits_per_unit is not UNSET: + field_dict["generation_credits_per_unit"] = generation_credits_per_unit + if generation_params is not UNSET: + field_dict["generation_params"] = generation_params + if generation_unit_label is not UNSET: + field_dict["generation_unit_label"] = generation_unit_label + if image_generation_tool_credits_per_image is not UNSET: + field_dict["image_generation_tool_credits_per_image"] = ( + image_generation_tool_credits_per_image + ) if input_1h_cache_write_credits_per_1000_tokens is not UNSET: field_dict["input_1h_cache_write_credits_per_1000_tokens"] = ( input_1h_cache_write_credits_per_1000_tokens @@ -424,6 +488,9 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.modality_rate_response import ModalityRateResponse + from ..models.prompt_model_response_generation_params_type_0 import ( + PromptModelResponseGenerationParamsType0, + ) from ..models.prompt_model_response_payload_schema_type_0 import ( PromptModelResponsePayloadSchemaType0, ) @@ -486,6 +553,64 @@ def _parse_family_generation(data: object) -> float | None | Unset: family_generation = _parse_family_generation(d.pop("family_generation", UNSET)) + def _parse_generation_credits_per_unit(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + generation_credits_per_unit = _parse_generation_credits_per_unit( + d.pop("generation_credits_per_unit", UNSET) + ) + + def _parse_generation_params( + data: object, + ) -> None | PromptModelResponseGenerationParamsType0 | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + generation_params_type_0 = ( + PromptModelResponseGenerationParamsType0.from_dict(data) + ) + + return generation_params_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(None | PromptModelResponseGenerationParamsType0 | Unset, data) + + generation_params = _parse_generation_params(d.pop("generation_params", UNSET)) + + def _parse_generation_unit_label(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + generation_unit_label = _parse_generation_unit_label( + d.pop("generation_unit_label", UNSET) + ) + + def _parse_image_generation_tool_credits_per_image( + data: object, + ) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + image_generation_tool_credits_per_image = ( + _parse_image_generation_tool_credits_per_image( + d.pop("image_generation_tool_credits_per_image", UNSET) + ) + ) + def _parse_input_1h_cache_write_credits_per_1000_tokens( data: object, ) -> float | None | Unset: @@ -822,6 +947,10 @@ def _parse_variants( deprecated_at=deprecated_at, family=family, family_generation=family_generation, + generation_credits_per_unit=generation_credits_per_unit, + generation_params=generation_params, + generation_unit_label=generation_unit_label, + image_generation_tool_credits_per_image=image_generation_tool_credits_per_image, input_1h_cache_write_credits_per_1000_tokens=input_1h_cache_write_credits_per_1000_tokens, input_5m_cache_write_credits_per_1000_tokens=input_5m_cache_write_credits_per_1000_tokens, input_cache_hit_credits_per_1000_tokens=input_cache_hit_credits_per_1000_tokens, diff --git a/seclai/_generated/models/prompt_model_response_generation_params_type_0.py b/seclai/_generated/models/prompt_model_response_generation_params_type_0.py new file mode 100644 index 0000000..ea79299 --- /dev/null +++ b/seclai/_generated/models/prompt_model_response_generation_params_type_0.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PromptModelResponseGenerationParamsType0") + + +@_attrs_define +class PromptModelResponseGenerationParamsType0: + """ """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + prompt_model_response_generation_params_type_0 = cls() + + prompt_model_response_generation_params_type_0.additional_properties = d + return prompt_model_response_generation_params_type_0 + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/remove_email_domain_response.py b/seclai/_generated/models/remove_email_domain_response.py new file mode 100644 index 0000000..c52e04c --- /dev/null +++ b/seclai/_generated/models/remove_email_domain_response.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="RemoveEmailDomainResponse") + + +@_attrs_define +class RemoveEmailDomainResponse: + """ + Attributes: + cleanup_note (None | str | Unset): + removed (bool | Unset): Default: True. + """ + + cleanup_note: None | str | Unset = UNSET + removed: bool | Unset = True + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + cleanup_note: None | str | Unset + if isinstance(self.cleanup_note, Unset): + cleanup_note = UNSET + else: + cleanup_note = self.cleanup_note + + removed = self.removed + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if cleanup_note is not UNSET: + field_dict["cleanup_note"] = cleanup_note + if removed is not UNSET: + field_dict["removed"] = removed + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + + def _parse_cleanup_note(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + cleanup_note = _parse_cleanup_note(d.pop("cleanup_note", UNSET)) + + removed = d.pop("removed", UNSET) + + remove_email_domain_response = cls( + cleanup_note=cleanup_note, + removed=removed, + ) + + remove_email_domain_response.additional_properties = d + return remove_email_domain_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/resume_inbound_response.py b/seclai/_generated/models/resume_inbound_response.py new file mode 100644 index 0000000..c404e2c --- /dev/null +++ b/seclai/_generated/models/resume_inbound_response.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="ResumeInboundResponse") + + +@_attrs_define +class ResumeInboundResponse: + """ + Attributes: + resumed (bool): + """ + + resumed: bool + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + resumed = self.resumed + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "resumed": resumed, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + resumed = d.pop("resumed") + + resume_inbound_response = cls( + resumed=resumed, + ) + + resume_inbound_response.additional_properties = d + return resume_inbound_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/send_test_email_response.py b/seclai/_generated/models/send_test_email_response.py new file mode 100644 index 0000000..d3ce45f --- /dev/null +++ b/seclai/_generated/models/send_test_email_response.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="SendTestEmailResponse") + + +@_attrs_define +class SendTestEmailResponse: + """ + Attributes: + sent (bool | Unset): Default: True. + """ + + sent: bool | Unset = True + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + sent = self.sent + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if sent is not UNSET: + field_dict["sent"] = sent + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + sent = d.pop("sent", UNSET) + + send_test_email_response = cls( + sent=sent, + ) + + send_test_email_response.additional_properties = d + return send_test_email_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/set_auto_block_mode_request.py b/seclai/_generated/models/set_auto_block_mode_request.py new file mode 100644 index 0000000..965161d --- /dev/null +++ b/seclai/_generated/models/set_auto_block_mode_request.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="SetAutoBlockModeRequest") + + +@_attrs_define +class SetAutoBlockModeRequest: + """Set the account's governance auto-block mode (shared REST request). + + Attributes: + mode (str): + """ + + mode: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + mode = self.mode + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "mode": mode, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + mode = d.pop("mode") + + set_auto_block_mode_request = cls( + mode=mode, + ) + + set_auto_block_mode_request.additional_properties = d + return set_auto_block_mode_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/set_email_trigger_config_request.py b/seclai/_generated/models/set_email_trigger_config_request.py new file mode 100644 index 0000000..ec192a6 --- /dev/null +++ b/seclai/_generated/models/set_email_trigger_config_request.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="SetEmailTriggerConfigRequest") + + +@_attrs_define +class SetEmailTriggerConfigRequest: + """Alias and/or sender allowlist for an EMAIL_RECEIVED trigger. + + A field omitted is left unchanged; passing ``null`` (or ``""`` for + ``alias``) clears it. + + Attributes: + alias (None | str | Unset): Custom alias for the address `.@agent.seclai.com` (alphanumeric + plus '+', '.', '-'; 1–32 chars; not starting/ending with '+', '.', '-'; not UUID-shaped). Pass null/empty to + clear. + allowed_senders (list[str] | None | Unset): Allowlist of full sender addresses and/or bare domains (a bare + domain also matches sub-domains). Empty/null accepts any sender. + ignore_auto_generated (bool | None | Unset): When true (default for new triggers), machine-generated inbound + mail (auto-replies, bulk/list mail, bounces) is dropped before a run to prevent auto-reply loops. Set false to + process automated mail. + queue_on_quota (bool | None | Unset): When true (default false), inbound mail that exceeds the account's hourly + email-trigger rate is parked in a QUEUED run and drained later by the catch-up sweep instead of being failed; + when false, over-rate mail fails immediately. + require_sender_auth (bool | None | Unset): When true (default for new triggers), the envelope sender must pass + SPF or DMARC even on an open inbox (no allowlist); unauthenticated, spoofable mail is rejected. Set false to + accept fully unauthenticated mail on an open inbox. + """ + + alias: None | str | Unset = UNSET + allowed_senders: list[str] | None | Unset = UNSET + ignore_auto_generated: bool | None | Unset = UNSET + queue_on_quota: bool | None | Unset = UNSET + require_sender_auth: bool | None | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + alias: None | str | Unset + if isinstance(self.alias, Unset): + alias = UNSET + else: + alias = self.alias + + allowed_senders: list[str] | None | Unset + if isinstance(self.allowed_senders, Unset): + allowed_senders = UNSET + elif isinstance(self.allowed_senders, list): + allowed_senders = self.allowed_senders + + else: + allowed_senders = self.allowed_senders + + ignore_auto_generated: bool | None | Unset + if isinstance(self.ignore_auto_generated, Unset): + ignore_auto_generated = UNSET + else: + ignore_auto_generated = self.ignore_auto_generated + + queue_on_quota: bool | None | Unset + if isinstance(self.queue_on_quota, Unset): + queue_on_quota = UNSET + else: + queue_on_quota = self.queue_on_quota + + require_sender_auth: bool | None | Unset + if isinstance(self.require_sender_auth, Unset): + require_sender_auth = UNSET + else: + require_sender_auth = self.require_sender_auth + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if alias is not UNSET: + field_dict["alias"] = alias + if allowed_senders is not UNSET: + field_dict["allowed_senders"] = allowed_senders + if ignore_auto_generated is not UNSET: + field_dict["ignore_auto_generated"] = ignore_auto_generated + if queue_on_quota is not UNSET: + field_dict["queue_on_quota"] = queue_on_quota + if require_sender_auth is not UNSET: + field_dict["require_sender_auth"] = require_sender_auth + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + + def _parse_alias(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + alias = _parse_alias(d.pop("alias", UNSET)) + + def _parse_allowed_senders(data: object) -> list[str] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + allowed_senders_type_0 = cast(list[str], data) + + return allowed_senders_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[str] | None | Unset, data) + + allowed_senders = _parse_allowed_senders(d.pop("allowed_senders", UNSET)) + + def _parse_ignore_auto_generated(data: object) -> bool | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(bool | None | Unset, data) + + ignore_auto_generated = _parse_ignore_auto_generated( + d.pop("ignore_auto_generated", UNSET) + ) + + def _parse_queue_on_quota(data: object) -> bool | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(bool | None | Unset, data) + + queue_on_quota = _parse_queue_on_quota(d.pop("queue_on_quota", UNSET)) + + def _parse_require_sender_auth(data: object) -> bool | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(bool | None | Unset, data) + + require_sender_auth = _parse_require_sender_auth( + d.pop("require_sender_auth", UNSET) + ) + + set_email_trigger_config_request = cls( + alias=alias, + allowed_senders=allowed_senders, + ignore_auto_generated=ignore_auto_generated, + queue_on_quota=queue_on_quota, + require_sender_auth=require_sender_auth, + ) + + set_email_trigger_config_request.additional_properties = d + return set_email_trigger_config_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/seclai/_generated/models/source_response.py b/seclai/_generated/models/source_response.py index 0aaa0c2..41dd46f 100644 --- a/seclai/_generated/models/source_response.py +++ b/seclai/_generated/models/source_response.py @@ -47,6 +47,8 @@ class SourceResponse: has_historical_data (bool | Unset): Indicates if the source connection has historical data. Default: False. index_mode (None | SourceIndexMode | Unset): Index mode for custom_index sources: fast_and_cheap, balanced, slow_and_thorough, or custom. + media_types (list[str] | Unset): Media kinds extracted from indexed content and embedded as multi-modal KB + chunks (subset of ['images', 'video']). Empty = text-only. readonly (bool | Unset): Indicates if the source connection is read-only. Default: False. system_managed (bool | Unset): Indicates if this source is automatically managed by the system (e.g., agent traces). Default: False. @@ -80,6 +82,7 @@ class SourceResponse: free_retention_days: int | None | Unset = UNSET has_historical_data: bool | Unset = False index_mode: None | SourceIndexMode | Unset = UNSET + media_types: list[str] | Unset = UNSET readonly: bool | Unset = False system_managed: bool | Unset = False additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -198,6 +201,10 @@ def to_dict(self) -> dict[str, Any]: else: index_mode = self.index_mode + media_types: list[str] | Unset = UNSET + if not isinstance(self.media_types, Unset): + media_types = self.media_types + readonly = self.readonly system_managed = self.system_managed @@ -250,6 +257,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["has_historical_data"] = has_historical_data if index_mode is not UNSET: field_dict["index_mode"] = index_mode + if media_types is not UNSET: + field_dict["media_types"] = media_types if readonly is not UNSET: field_dict["readonly"] = readonly if system_managed is not UNSET: @@ -453,6 +462,8 @@ def _parse_index_mode(data: object) -> None | SourceIndexMode | Unset: index_mode = _parse_index_mode(d.pop("index_mode", UNSET)) + media_types = cast(list[str], d.pop("media_types", UNSET)) + readonly = d.pop("readonly", UNSET) system_managed = d.pop("system_managed", UNSET) @@ -486,6 +497,7 @@ def _parse_index_mode(data: object) -> None | SourceIndexMode | Unset: free_retention_days=free_retention_days, has_historical_data=has_historical_data, index_mode=index_mode, + media_types=media_types, readonly=readonly, system_managed=system_managed, ) diff --git a/seclai/_generated/models/update_source_body.py b/seclai/_generated/models/update_source_body.py index 74d630c..19039bc 100644 --- a/seclai/_generated/models/update_source_body.py +++ b/seclai/_generated/models/update_source_body.py @@ -16,17 +16,30 @@ class UpdateSourceBody: """Request body for updating a content source. Attributes: + media_types (list[str] | None | Unset): Media kinds to extract from indexed content and embed as multi-modal KB + chunks. Subset of ['images', 'video']. Only kinds the source's embedder can index are honored; unsupported + values are dropped. [] disables media extraction (text-only). name (None | str | Unset): New name. polling (None | str | Unset): New polling interval. retention_days (int | None | Unset): New retention period in days (null for unlimited). Default: -1. """ + media_types: list[str] | None | Unset = UNSET name: None | str | Unset = UNSET polling: None | str | Unset = UNSET retention_days: int | None | Unset = -1 additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + media_types: list[str] | None | Unset + if isinstance(self.media_types, Unset): + media_types = UNSET + elif isinstance(self.media_types, list): + media_types = self.media_types + + else: + media_types = self.media_types + name: None | str | Unset if isinstance(self.name, Unset): name = UNSET @@ -48,6 +61,8 @@ def to_dict(self) -> dict[str, Any]: field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if media_types is not UNSET: + field_dict["media_types"] = media_types if name is not UNSET: field_dict["name"] = name if polling is not UNSET: @@ -61,6 +76,23 @@ def to_dict(self) -> dict[str, Any]: def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: d = dict(src_dict) + def _parse_media_types(data: object) -> list[str] | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, list): + raise TypeError() + media_types_type_0 = cast(list[str], data) + + return media_types_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[str] | None | Unset, data) + + media_types = _parse_media_types(d.pop("media_types", UNSET)) + def _parse_name(data: object) -> None | str | Unset: if data is None: return data @@ -89,6 +121,7 @@ def _parse_retention_days(data: object) -> int | None | Unset: retention_days = _parse_retention_days(d.pop("retention_days", UNSET)) update_source_body = cls( + media_types=media_types, name=name, polling=polling, retention_days=retention_days, diff --git a/seclai/seclai.py b/seclai/seclai.py index 4d3ad84..bed2cc8 100644 --- a/seclai/seclai.py +++ b/seclai/seclai.py @@ -1541,6 +1541,17 @@ def upload_file_to_content( if created_payload is not None: created_payload.close() + # ── Identity ────────────────────────────────────────────────────────────────── + + def get_me(self) -> dict[str, Any]: + """Get the authenticated user's identity. + + Returns: + The caller's personal ``account_id`` and the organizations they belong to. + + """ + return cast(dict[str, Any], self.request("GET", "/me")) + # ── Agents ──────────────────────────────────────────────────────────────── def list_agents(self, *, page: int = 1, limit: int = 50) -> dict[str, Any]: @@ -1606,6 +1617,45 @@ def delete_agent(self, agent_id: str) -> None: """ self.request("DELETE", f"/agents/{agent_id}") + def disable_agent(self, agent_id: str) -> dict[str, Any]: + """Pause (disable) an agent so it stops firing from every trigger path. + + Args: + agent_id: Agent identifier. + + Returns: + The updated agent summary. + + """ + return cast(dict[str, Any], self.request("POST", f"/agents/{agent_id}/disable")) + + def enable_agent(self, agent_id: str) -> dict[str, Any]: + """Resume (enable) a paused agent. + + Args: + agent_id: Agent identifier. + + Returns: + The updated agent summary. + + """ + return cast(dict[str, Any], self.request("POST", f"/agents/{agent_id}/enable")) + + def get_agent_callers(self, agent_id: str) -> list[dict[str, Any]]: + """List the live agents that call this agent via a ``call_agent`` step. + + Args: + agent_id: Agent identifier. + + Returns: + The calling agents; each must be disabled before this agent can be paused. + + """ + return cast( + list[dict[str, Any]], + self.request("GET", f"/agents/{agent_id}/callers"), + ) + # ── Agent Export ──────────────────────────────────────────────────────────── def export_agent(self, agent_id: str, *, download: bool = True) -> dict[str, Any]: @@ -2197,6 +2247,198 @@ def get_non_manual_evaluation_summary(self, agent_id: str) -> dict[str, Any]: ), ) + # ── Agent Email Governance ──────────────────────────────────────────────────── + + def list_agent_email_optouts( + self, + *, + agent_id: str | None = None, + limit: int | None = None, + offset: int | None = None, + ) -> dict[str, Any]: + """List recipients who have opted out of this account's agent emails. + + Args: + agent_id: Filter to one agent (account-wide opt-outs still apply). + limit: Page size (1-200, default 50). + offset: Rows to skip. + + Returns: + The page of opt-outs plus the total count. + + """ + return cast( + dict[str, Any], + self.request( + "GET", + "/agents/agent-email-optouts", + params=_strip_none( + {"agent_id": agent_id, "limit": limit, "offset": offset} + ), + ), + ) + + def remove_agent_email_optout(self, optout_id: str) -> None: + """Revoke an opt-out, opting the recipient back in to agent emails. + + Args: + optout_id: Opt-out identifier. + + """ + self.request("DELETE", f"/agents/agent-email-optouts/{optout_id}") + + def list_blocked_email_senders( + self, + *, + limit: int | None = None, + offset: int | None = None, + ) -> dict[str, Any]: + """List the account's blocked inbound email senders and the auto-block mode. + + Args: + limit: Page size (1-200, default 50). + offset: Rows to skip. + + Returns: + The page of blocked senders plus ``auto_block_mode``. + + """ + return cast( + dict[str, Any], + self.request( + "GET", + "/agents/blocked-email-senders", + params=_strip_none({"limit": limit, "offset": offset}), + ), + ) + + def block_email_sender(self, body: dict[str, Any]) -> dict[str, Any]: + """Add a sender address or a whole domain to the account blocklist. + + Args: + body: ``sender_email`` plus optional ``match_type`` (``address`` + default, or ``domain``) and ``note``. + + Returns: + The blocked-sender entry. + + """ + return cast( + dict[str, Any], + self.request("POST", "/agents/blocked-email-senders", json=body), + ) + + def unblock_email_sender(self, blocked_id: str) -> None: + """Remove a blocked sender by id. + + Args: + blocked_id: Blocked-sender identifier. + + """ + self.request("DELETE", f"/agents/blocked-email-senders/{blocked_id}") + + def set_auto_block_mode(self, body: dict[str, Any]) -> dict[str, Any]: + """Set whether a governance BLOCK auto-adds the sender to the blocklist. + + Args: + body: ``mode``: ``disabled``, ``input``, or ``input_and_output``. + + Returns: + The updated blocked-sender list. + + """ + return cast( + dict[str, Any], + self.request("PUT", "/agents/blocked-email-senders/mode", json=body), + ) + + def list_inbound_email_rejections( + self, + *, + agent_id: str | None = None, + limit: int | None = None, + ) -> list[dict[str, Any]]: + """List recent inbound emails discarded before running an agent. + + Args: + agent_id: Filter to a single agent's rejections. + limit: Maximum results (1-200, default 50). + + Returns: + The discarded inbound emails. + + """ + return cast( + list[dict[str, Any]], + self.request( + "GET", + "/agents/inbound-email-rejections", + params=_strip_none({"agent_id": agent_id, "limit": limit}), + ), + ) + + def get_inbound_email_status(self) -> dict[str, Any]: + """Get the account's inbound-email overload status. + + Returns: + Whether the breaker has paused inbound mail, and the queued backlog size. + + """ + return cast(dict[str, Any], self.request("GET", "/agents/inbound-email-status")) + + def cancel_queued_email_runs(self) -> dict[str, Any]: + """Fail all of the account's QUEUED (over-quota parked) inbound-email runs. + + Returns: + The count cancelled. + + """ + return cast( + dict[str, Any], + self.request("POST", "/agents/inbound-email-status/cancel-queued"), + ) + + def resume_inbound_email(self) -> dict[str, Any]: + """Manually lift the account-wide inbound-email pause. + + Returns: + Whether the pause was lifted. One-shot: the breaker re-arms if still overloaded. + + """ + return cast( + dict[str, Any], + self.request("POST", "/agents/inbound-email-status/resume"), + ) + + # ── Agent Email Triggers ────────────────────────────────────────────────────── + + def set_email_trigger_config( + self, + agent_id: str, + trigger_id: str, + body: dict[str, Any], + ) -> dict[str, Any]: + """Configure an agent's EMAIL_RECEIVED trigger. + + Args: + agent_id: Agent identifier. + trigger_id: Trigger identifier. + body: Fields to set (alias, allowed_senders, ignore_auto_generated, + require_sender_auth, queue_on_quota). Omitted fields are unchanged. + + Returns: + The trigger's resolved email address(es) and config. + + """ + return cast( + dict[str, Any], + self.request( + "PUT", + f"/agents/{agent_id}/triggers/{trigger_id}/email-config", + json=body, + ), + ) + # ── Knowledge Bases ─────────────────────────────────────────────────────── def list_knowledge_bases( @@ -3498,6 +3740,15 @@ def get_model(self, model_id: str) -> JSONValue: """ return self.request("GET", f"/models/{model_id}/details") + def get_generation_tiers(self) -> dict[str, Any]: + """List the media-generation quality tiers and what each resolves to. + + Returns: + Each ``(modality, tier)`` mapped to its generator, credits, and price label. + + """ + return cast(dict[str, Any], self.request("GET", "/models/generation-tiers")) + # ── Model Playground Experiments ────────────────────────────────────────── def list_experiments( @@ -3608,6 +3859,148 @@ def search( ), ) + # ── Email Domains ───────────────────────────────────────────────────────────── + + def list_email_domains(self) -> dict[str, Any]: + """List the account's agent-email domains and plan capabilities. + + Returns: + Domains with verification status and required DNS records. + + """ + return cast(dict[str, Any], self.request("GET", "/email-domains")) + + def add_email_domain(self, body: dict[str, Any]) -> dict[str, Any]: + """Add and provision a vanity or custom agent-email domain. + + Args: + body: ``kind`` (``vanity``/``custom``) and ``value``; optional + ``delegated`` (default false) to let Seclai manage the zone. + + Returns: + The new domain, including the DNS records to publish. + + """ + return cast(dict[str, Any], self.request("POST", "/email-domains", json=body)) + + def remove_email_domain(self, domain_id: str) -> dict[str, Any]: + """Remove a domain and tear down its SES identity and DNS. + + Args: + domain_id: Domain identifier. + + Returns: + ``removed``, plus a ``cleanup_note`` when the domain was Seclai-managed. + + """ + return cast( + dict[str, Any], + self.request("DELETE", f"/email-domains/{domain_id}"), + ) + + def verify_email_domain(self, domain_id: str) -> dict[str, Any]: + """Re-poll SES and DNS for this domain now ('Check now'). + + Args: + domain_id: Domain identifier. + + Returns: + The updated status and DNS-record check results. + + """ + return cast( + dict[str, Any], + self.request("POST", f"/email-domains/{domain_id}/verify"), + ) + + def set_primary_email_domain(self, domain_id: str) -> dict[str, Any]: + """Promote a verified domain to the account's primary domain. + + Args: + domain_id: Domain identifier; must already be verified. + + Returns: + The updated domain. + + """ + return cast( + dict[str, Any], + self.request("POST", f"/email-domains/{domain_id}/primary"), + ) + + def use_shared_email_domain(self) -> None: + """Revert to the shared ``agent.seclai.com`` sending/inbound domain.""" + self.request("POST", "/email-domains/use-shared-domain") + + def send_email_domain_test_email(self, domain_id: str) -> dict[str, Any]: + """Send a test message from a verified domain to the account owner. + + Args: + domain_id: Domain identifier. + + Returns: + Whether the message was sent. + + """ + return cast( + dict[str, Any], + self.request("POST", f"/email-domains/{domain_id}/test-email"), + ) + + def get_dmarc_summary( + self, + domain_id: str, + *, + days: int | None = None, + top_sources: int | None = None, + ) -> dict[str, Any]: + """Get the DMARC aggregate-report summary for a domain. + + Args: + domain_id: Domain identifier. + days: Window length in days (default 30). + top_sources: Number of top failing sources to return (default 10). + + Returns: + Pass rate, disposition breakdown, and top failing source IPs. + + """ + return cast( + dict[str, Any], + self.request( + "GET", + f"/email-domains/{domain_id}/dmarc", + params=_strip_none({"days": days, "top_sources": top_sources}), + ), + ) + + def search_docs( + self, + query: str, + *, + mode: str | None = None, + limit: int | None = None, + ) -> dict[str, Any]: + """Search the Seclai documentation by content. + + Args: + query: Search query (1-200 chars). + mode: ``keyword`` (default; titles and summaries) or ``semantic`` (body). + limit: Maximum results (1-20, default 8). + + Returns: + Matching pages, each with a ``doc_slug`` and optional section ``anchor``. + + """ + return cast( + dict[str, Any], + self.request( + "GET", + "/docs-search", + params=_strip_none({"q": query, "mode": mode, "limit": limit}), + ), + ) + # ── Top-level AI Assistant ───────────────────────────────────────────────── def submit_ai_feedback(self, body: dict[str, Any]) -> dict[str, Any]: @@ -4959,10 +5352,21 @@ async def upload_file_to_content( if created_payload is not None: created_payload.close() - # ── Agents ──────────────────────────────────────────────────────────────── + # ── Identity ────────────────────────────────────────────────────────────────── - async def list_agents(self, *, page: int = 1, limit: int = 50) -> dict[str, Any]: - """List all agents. + async def get_me(self) -> dict[str, Any]: + """Get the authenticated user's identity. + + Returns: + The caller's personal ``account_id`` and the organizations they belong to. + + """ + return cast(dict[str, Any], await self.request("GET", "/me")) + + # ── Agents ──────────────────────────────────────────────────────────────── + + async def list_agents(self, *, page: int = 1, limit: int = 50) -> dict[str, Any]: + """List all agents. Args: page: Page number (1-indexed). @@ -5024,6 +5428,51 @@ async def delete_agent(self, agent_id: str) -> None: """ await self.request("DELETE", f"/agents/{agent_id}") + async def disable_agent(self, agent_id: str) -> dict[str, Any]: + """Pause (disable) an agent so it stops firing from every trigger path. + + Args: + agent_id: Agent identifier. + + Returns: + The updated agent summary. + + """ + return cast( + dict[str, Any], + await self.request("POST", f"/agents/{agent_id}/disable"), + ) + + async def enable_agent(self, agent_id: str) -> dict[str, Any]: + """Resume (enable) a paused agent. + + Args: + agent_id: Agent identifier. + + Returns: + The updated agent summary. + + """ + return cast( + dict[str, Any], + await self.request("POST", f"/agents/{agent_id}/enable"), + ) + + async def get_agent_callers(self, agent_id: str) -> list[dict[str, Any]]: + """List the live agents that call this agent via a ``call_agent`` step. + + Args: + agent_id: Agent identifier. + + Returns: + The calling agents; each must be disabled before this agent can be paused. + + """ + return cast( + list[dict[str, Any]], + await self.request("GET", f"/agents/{agent_id}/callers"), + ) + # ── Agent Export ────────────────────────────────────────────────────────── async def export_agent( @@ -5625,6 +6074,201 @@ async def get_non_manual_evaluation_summary(self, agent_id: str) -> dict[str, An ), ) + # ── Agent Email Governance ──────────────────────────────────────────────────── + + async def list_agent_email_optouts( + self, + *, + agent_id: str | None = None, + limit: int | None = None, + offset: int | None = None, + ) -> dict[str, Any]: + """List recipients who have opted out of this account's agent emails. + + Args: + agent_id: Filter to one agent (account-wide opt-outs still apply). + limit: Page size (1-200, default 50). + offset: Rows to skip. + + Returns: + The page of opt-outs plus the total count. + + """ + return cast( + dict[str, Any], + await self.request( + "GET", + "/agents/agent-email-optouts", + params=_strip_none( + {"agent_id": agent_id, "limit": limit, "offset": offset} + ), + ), + ) + + async def remove_agent_email_optout(self, optout_id: str) -> None: + """Revoke an opt-out, opting the recipient back in to agent emails. + + Args: + optout_id: Opt-out identifier. + + """ + await self.request("DELETE", f"/agents/agent-email-optouts/{optout_id}") + + async def list_blocked_email_senders( + self, + *, + limit: int | None = None, + offset: int | None = None, + ) -> dict[str, Any]: + """List the account's blocked inbound email senders and the auto-block mode. + + Args: + limit: Page size (1-200, default 50). + offset: Rows to skip. + + Returns: + The page of blocked senders plus ``auto_block_mode``. + + """ + return cast( + dict[str, Any], + await self.request( + "GET", + "/agents/blocked-email-senders", + params=_strip_none({"limit": limit, "offset": offset}), + ), + ) + + async def block_email_sender(self, body: dict[str, Any]) -> dict[str, Any]: + """Add a sender address or a whole domain to the account blocklist. + + Args: + body: ``sender_email`` plus optional ``match_type`` (``address`` + default, or ``domain``) and ``note``. + + Returns: + The blocked-sender entry. + + """ + return cast( + dict[str, Any], + await self.request("POST", "/agents/blocked-email-senders", json=body), + ) + + async def unblock_email_sender(self, blocked_id: str) -> None: + """Remove a blocked sender by id. + + Args: + blocked_id: Blocked-sender identifier. + + """ + await self.request("DELETE", f"/agents/blocked-email-senders/{blocked_id}") + + async def set_auto_block_mode(self, body: dict[str, Any]) -> dict[str, Any]: + """Set whether a governance BLOCK auto-adds the sender to the blocklist. + + Args: + body: ``mode``: ``disabled``, ``input``, or ``input_and_output``. + + Returns: + The updated blocked-sender list. + + """ + return cast( + dict[str, Any], + await self.request("PUT", "/agents/blocked-email-senders/mode", json=body), + ) + + async def list_inbound_email_rejections( + self, + *, + agent_id: str | None = None, + limit: int | None = None, + ) -> list[dict[str, Any]]: + """List recent inbound emails discarded before running an agent. + + Args: + agent_id: Filter to a single agent's rejections. + limit: Maximum results (1-200, default 50). + + Returns: + The discarded inbound emails. + + """ + return cast( + list[dict[str, Any]], + await self.request( + "GET", + "/agents/inbound-email-rejections", + params=_strip_none({"agent_id": agent_id, "limit": limit}), + ), + ) + + async def get_inbound_email_status(self) -> dict[str, Any]: + """Get the account's inbound-email overload status. + + Returns: + Whether the breaker has paused inbound mail, and the queued backlog size. + + """ + return cast( + dict[str, Any], + await self.request("GET", "/agents/inbound-email-status"), + ) + + async def cancel_queued_email_runs(self) -> dict[str, Any]: + """Fail all of the account's QUEUED (over-quota parked) inbound-email runs. + + Returns: + The count cancelled. + + """ + return cast( + dict[str, Any], + await self.request("POST", "/agents/inbound-email-status/cancel-queued"), + ) + + async def resume_inbound_email(self) -> dict[str, Any]: + """Manually lift the account-wide inbound-email pause. + + Returns: + Whether the pause was lifted. One-shot: the breaker re-arms if still overloaded. + + """ + return cast( + dict[str, Any], + await self.request("POST", "/agents/inbound-email-status/resume"), + ) + + # ── Agent Email Triggers ────────────────────────────────────────────────────── + + async def set_email_trigger_config( + self, + agent_id: str, + trigger_id: str, + body: dict[str, Any], + ) -> dict[str, Any]: + """Configure an agent's EMAIL_RECEIVED trigger. + + Args: + agent_id: Agent identifier. + trigger_id: Trigger identifier. + body: Fields to set (alias, allowed_senders, ignore_auto_generated, + require_sender_auth, queue_on_quota). Omitted fields are unchanged. + + Returns: + The trigger's resolved email address(es) and config. + + """ + return cast( + dict[str, Any], + await self.request( + "PUT", + f"/agents/{agent_id}/triggers/{trigger_id}/email-config", + json=body, + ), + ) + # ── Knowledge Bases ─────────────────────────────────────────────────────── async def list_knowledge_bases( @@ -6953,6 +7597,18 @@ async def get_model(self, model_id: str) -> JSONValue: """ return await self.request("GET", f"/models/{model_id}/details") + async def get_generation_tiers(self) -> dict[str, Any]: + """List the media-generation quality tiers and what each resolves to. + + Returns: + Each ``(modality, tier)`` mapped to its generator, credits, and price label. + + """ + return cast( + dict[str, Any], + await self.request("GET", "/models/generation-tiers"), + ) + # ── Model Playground Experiments ────────────────────────────────────────── async def list_experiments( @@ -7065,6 +7721,151 @@ async def search( ), ) + # ── Email Domains ───────────────────────────────────────────────────────────── + + async def list_email_domains(self) -> dict[str, Any]: + """List the account's agent-email domains and plan capabilities. + + Returns: + Domains with verification status and required DNS records. + + """ + return cast(dict[str, Any], await self.request("GET", "/email-domains")) + + async def add_email_domain(self, body: dict[str, Any]) -> dict[str, Any]: + """Add and provision a vanity or custom agent-email domain. + + Args: + body: ``kind`` (``vanity``/``custom``) and ``value``; optional + ``delegated`` (default false) to let Seclai manage the zone. + + Returns: + The new domain, including the DNS records to publish. + + """ + return cast( + dict[str, Any], + await self.request("POST", "/email-domains", json=body), + ) + + async def remove_email_domain(self, domain_id: str) -> dict[str, Any]: + """Remove a domain and tear down its SES identity and DNS. + + Args: + domain_id: Domain identifier. + + Returns: + ``removed``, plus a ``cleanup_note`` when the domain was Seclai-managed. + + """ + return cast( + dict[str, Any], + await self.request("DELETE", f"/email-domains/{domain_id}"), + ) + + async def verify_email_domain(self, domain_id: str) -> dict[str, Any]: + """Re-poll SES and DNS for this domain now ('Check now'). + + Args: + domain_id: Domain identifier. + + Returns: + The updated status and DNS-record check results. + + """ + return cast( + dict[str, Any], + await self.request("POST", f"/email-domains/{domain_id}/verify"), + ) + + async def set_primary_email_domain(self, domain_id: str) -> dict[str, Any]: + """Promote a verified domain to the account's primary domain. + + Args: + domain_id: Domain identifier; must already be verified. + + Returns: + The updated domain. + + """ + return cast( + dict[str, Any], + await self.request("POST", f"/email-domains/{domain_id}/primary"), + ) + + async def use_shared_email_domain(self) -> None: + """Revert to the shared ``agent.seclai.com`` sending/inbound domain.""" + await self.request("POST", "/email-domains/use-shared-domain") + + async def send_email_domain_test_email(self, domain_id: str) -> dict[str, Any]: + """Send a test message from a verified domain to the account owner. + + Args: + domain_id: Domain identifier. + + Returns: + Whether the message was sent. + + """ + return cast( + dict[str, Any], + await self.request("POST", f"/email-domains/{domain_id}/test-email"), + ) + + async def get_dmarc_summary( + self, + domain_id: str, + *, + days: int | None = None, + top_sources: int | None = None, + ) -> dict[str, Any]: + """Get the DMARC aggregate-report summary for a domain. + + Args: + domain_id: Domain identifier. + days: Window length in days (default 30). + top_sources: Number of top failing sources to return (default 10). + + Returns: + Pass rate, disposition breakdown, and top failing source IPs. + + """ + return cast( + dict[str, Any], + await self.request( + "GET", + f"/email-domains/{domain_id}/dmarc", + params=_strip_none({"days": days, "top_sources": top_sources}), + ), + ) + + async def search_docs( + self, + query: str, + *, + mode: str | None = None, + limit: int | None = None, + ) -> dict[str, Any]: + """Search the Seclai documentation by content. + + Args: + query: Search query (1-200 chars). + mode: ``keyword`` (default; titles and summaries) or ``semantic`` (body). + limit: Maximum results (1-20, default 8). + + Returns: + Matching pages, each with a ``doc_slug`` and optional section ``anchor``. + + """ + return cast( + dict[str, Any], + await self.request( + "GET", + "/docs-search", + params=_strip_none({"q": query, "mode": mode, "limit": limit}), + ), + ) + # ── Top-level AI Assistant ───────────────────────────────────────────────── async def submit_ai_feedback(self, body: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/test_new_methods.py b/tests/test_new_methods.py index e36dd06..c09f774 100644 --- a/tests/test_new_methods.py +++ b/tests/test_new_methods.py @@ -2113,3 +2113,1001 @@ async def handler(req: httpx.Request) -> httpx.Response: with pytest.raises(SeclaiAPIStatusError) as exc: await client.list_agents() assert exc.value.status_code == 502 + + +# --------------------------------------------------------------------------- +# New in this sync: identity, agent pause, email governance, email domains, +# generation tiers, docs search +# --------------------------------------------------------------------------- + + +class TestIdentity: + """Identity.""" + + def test_get_me(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"account_id": "acct_1", "organizations": []}, status=200 + ) + + client = _sync_client(handler) + result = client.get_me() + assert seen["method"] == "GET" + assert seen["path"] == "/me" + assert result == {"account_id": "acct_1", "organizations": []} + + @pytest.mark.asyncio + async def test_async_get_me(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"account_id": "acct_1", "organizations": []}, status=200 + ) + + client = _async_client(handler) + result = await client.get_me() + assert seen["method"] == "GET" + assert seen["path"] == "/me" + assert result == {"account_id": "acct_1", "organizations": []} + + +class TestAgentEnableDisable: + """Agents — enable / disable.""" + + def test_disable_agent(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "a1", "disabled": True}, status=200) + + client = _sync_client(handler) + result = client.disable_agent("a1") + assert seen["method"] == "POST" + assert seen["path"] == "/agents/a1/disable" + assert result == {"id": "a1", "disabled": True} + + def test_enable_agent(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "a1", "disabled": False}, status=200) + + client = _sync_client(handler) + result = client.enable_agent("a1") + assert seen["method"] == "POST" + assert seen["path"] == "/agents/a1/enable" + assert result == {"id": "a1", "disabled": False} + + def test_get_agent_callers(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + [{"id": "a2", "name": "Caller", "disabled": False}], status=200 + ) + + client = _sync_client(handler) + result = client.get_agent_callers("a1") + assert seen["method"] == "GET" + assert seen["path"] == "/agents/a1/callers" + assert result == [{"id": "a2", "name": "Caller", "disabled": False}] + + @pytest.mark.asyncio + async def test_async_disable_agent(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "a1", "disabled": True}, status=200) + + client = _async_client(handler) + result = await client.disable_agent("a1") + assert seen["method"] == "POST" + assert seen["path"] == "/agents/a1/disable" + assert result == {"id": "a1", "disabled": True} + + @pytest.mark.asyncio + async def test_async_enable_agent(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "a1", "disabled": False}, status=200) + + client = _async_client(handler) + result = await client.enable_agent("a1") + assert seen["method"] == "POST" + assert seen["path"] == "/agents/a1/enable" + assert result == {"id": "a1", "disabled": False} + + @pytest.mark.asyncio + async def test_async_get_agent_callers(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + [{"id": "a2", "name": "Caller", "disabled": False}], status=200 + ) + + client = _async_client(handler) + result = await client.get_agent_callers("a1") + assert seen["method"] == "GET" + assert seen["path"] == "/agents/a1/callers" + assert result == [{"id": "a2", "name": "Caller", "disabled": False}] + + +class TestAgentEmailTriggers: + """Agent email triggers.""" + + def test_set_email_trigger_config(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + { + "trigger_id": "t1", + "agent_id": "a1", + "email_addresses": ["support.acct@agent.seclai.com"], + }, + status=200, + ) + + client = _sync_client(handler) + result = client.set_email_trigger_config("a1", "t1", {"alias": "support"}) + assert seen["method"] == "PUT" + assert seen["path"] == "/agents/a1/triggers/t1/email-config" + assert seen["body"] == {"alias": "support"} + assert result == { + "trigger_id": "t1", + "agent_id": "a1", + "email_addresses": ["support.acct@agent.seclai.com"], + } + + @pytest.mark.asyncio + async def test_async_set_email_trigger_config(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + { + "trigger_id": "t1", + "agent_id": "a1", + "email_addresses": ["support.acct@agent.seclai.com"], + }, + status=200, + ) + + client = _async_client(handler) + result = await client.set_email_trigger_config("a1", "t1", {"alias": "support"}) + assert seen["method"] == "PUT" + assert seen["path"] == "/agents/a1/triggers/t1/email-config" + assert seen["body"] == {"alias": "support"} + assert result == { + "trigger_id": "t1", + "agent_id": "a1", + "email_addresses": ["support.acct@agent.seclai.com"], + } + + +class TestAgentEmailGovernance: + """Agent email governance.""" + + def test_list_agent_email_optouts(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"items": [], "total": 0}, status=200) + + client = _sync_client(handler) + result = client.list_agent_email_optouts(agent_id="a1", limit=25, offset=50) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/agent-email-optouts" + assert seen["params"] == {"agent_id": "a1", "limit": "25", "offset": "50"} + assert result == {"items": [], "total": 0} + + def test_list_agent_email_optouts_omits_unset(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"items": [], "total": 0}, status=200) + + client = _sync_client(handler) + result = client.list_agent_email_optouts() + assert seen["method"] == "GET" + assert seen["path"] == "/agents/agent-email-optouts" + assert seen["params"] == {} + assert result == {"items": [], "total": 0} + + def test_remove_agent_email_optout(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _sync_client(handler) + client.remove_agent_email_optout("oo1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/agents/agent-email-optouts/oo1" + + def test_list_blocked_email_senders(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + {"items": [], "total": 0, "auto_block_mode": "disabled"}, status=200 + ) + + client = _sync_client(handler) + result = client.list_blocked_email_senders(limit=10) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/blocked-email-senders" + assert seen["params"] == {"limit": "10"} + assert result == {"items": [], "total": 0, "auto_block_mode": "disabled"} + + def test_block_email_sender(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + { + "id": "b1", + "sender_email": "spam@example.com", + "match_type": "domain", + }, + status=201, + ) + + client = _sync_client(handler) + result = client.block_email_sender( + {"sender_email": "spam@example.com", "match_type": "domain"} + ) + assert seen["method"] == "POST" + assert seen["path"] == "/agents/blocked-email-senders" + assert seen["body"]["match_type"] == "domain" + assert result == { + "id": "b1", + "sender_email": "spam@example.com", + "match_type": "domain", + } + + def test_unblock_email_sender(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _sync_client(handler) + client.unblock_email_sender("b1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/agents/blocked-email-senders/b1" + + def test_set_auto_block_mode(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"items": [], "total": 0, "auto_block_mode": "input_and_output"}, + status=200, + ) + + client = _sync_client(handler) + result = client.set_auto_block_mode({"mode": "input_and_output"}) + assert seen["method"] == "PUT" + assert seen["path"] == "/agents/blocked-email-senders/mode" + assert seen["body"] == {"mode": "input_and_output"} + assert result == { + "items": [], + "total": 0, + "auto_block_mode": "input_and_output", + } + + def test_list_inbound_email_rejections(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + [{"id": "r1", "reason": "unauthorized_sender"}], status=200 + ) + + client = _sync_client(handler) + result = client.list_inbound_email_rejections(agent_id="a1", limit=5) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/inbound-email-rejections" + assert seen["params"] == {"agent_id": "a1", "limit": "5"} + assert result == [{"id": "r1", "reason": "unauthorized_sender"}] + + def test_get_inbound_email_status(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"paused": True, "queued_backlog": 42}, status=200) + + client = _sync_client(handler) + result = client.get_inbound_email_status() + assert seen["method"] == "GET" + assert seen["path"] == "/agents/inbound-email-status" + assert result == {"paused": True, "queued_backlog": 42} + + def test_cancel_queued_email_runs(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"cancelled": 7}, status=200) + + client = _sync_client(handler) + result = client.cancel_queued_email_runs() + assert seen["method"] == "POST" + assert seen["path"] == "/agents/inbound-email-status/cancel-queued" + assert result == {"cancelled": 7} + + def test_resume_inbound_email(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"resumed": True}, status=200) + + client = _sync_client(handler) + result = client.resume_inbound_email() + assert seen["method"] == "POST" + assert seen["path"] == "/agents/inbound-email-status/resume" + assert result == {"resumed": True} + + @pytest.mark.asyncio + async def test_async_list_agent_email_optouts(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"items": [], "total": 0}, status=200) + + client = _async_client(handler) + result = await client.list_agent_email_optouts( + agent_id="a1", limit=25, offset=50 + ) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/agent-email-optouts" + assert seen["params"] == {"agent_id": "a1", "limit": "25", "offset": "50"} + assert result == {"items": [], "total": 0} + + @pytest.mark.asyncio + async def test_async_list_agent_email_optouts_omits_unset(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"items": [], "total": 0}, status=200) + + client = _async_client(handler) + result = await client.list_agent_email_optouts() + assert seen["method"] == "GET" + assert seen["path"] == "/agents/agent-email-optouts" + assert seen["params"] == {} + assert result == {"items": [], "total": 0} + + @pytest.mark.asyncio + async def test_async_remove_agent_email_optout(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _async_client(handler) + await client.remove_agent_email_optout("oo1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/agents/agent-email-optouts/oo1" + + @pytest.mark.asyncio + async def test_async_list_blocked_email_senders(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + {"items": [], "total": 0, "auto_block_mode": "disabled"}, status=200 + ) + + client = _async_client(handler) + result = await client.list_blocked_email_senders(limit=10) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/blocked-email-senders" + assert seen["params"] == {"limit": "10"} + assert result == {"items": [], "total": 0, "auto_block_mode": "disabled"} + + @pytest.mark.asyncio + async def test_async_block_email_sender(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + { + "id": "b1", + "sender_email": "spam@example.com", + "match_type": "domain", + }, + status=201, + ) + + client = _async_client(handler) + result = await client.block_email_sender( + {"sender_email": "spam@example.com", "match_type": "domain"} + ) + assert seen["method"] == "POST" + assert seen["path"] == "/agents/blocked-email-senders" + assert seen["body"]["match_type"] == "domain" + assert result == { + "id": "b1", + "sender_email": "spam@example.com", + "match_type": "domain", + } + + @pytest.mark.asyncio + async def test_async_unblock_email_sender(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _async_client(handler) + await client.unblock_email_sender("b1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/agents/blocked-email-senders/b1" + + @pytest.mark.asyncio + async def test_async_set_auto_block_mode(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"items": [], "total": 0, "auto_block_mode": "input_and_output"}, + status=200, + ) + + client = _async_client(handler) + result = await client.set_auto_block_mode({"mode": "input_and_output"}) + assert seen["method"] == "PUT" + assert seen["path"] == "/agents/blocked-email-senders/mode" + assert seen["body"] == {"mode": "input_and_output"} + assert result == { + "items": [], + "total": 0, + "auto_block_mode": "input_and_output", + } + + @pytest.mark.asyncio + async def test_async_list_inbound_email_rejections(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + [{"id": "r1", "reason": "unauthorized_sender"}], status=200 + ) + + client = _async_client(handler) + result = await client.list_inbound_email_rejections(agent_id="a1", limit=5) + assert seen["method"] == "GET" + assert seen["path"] == "/agents/inbound-email-rejections" + assert seen["params"] == {"agent_id": "a1", "limit": "5"} + assert result == [{"id": "r1", "reason": "unauthorized_sender"}] + + @pytest.mark.asyncio + async def test_async_get_inbound_email_status(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"paused": True, "queued_backlog": 42}, status=200) + + client = _async_client(handler) + result = await client.get_inbound_email_status() + assert seen["method"] == "GET" + assert seen["path"] == "/agents/inbound-email-status" + assert result == {"paused": True, "queued_backlog": 42} + + @pytest.mark.asyncio + async def test_async_cancel_queued_email_runs(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"cancelled": 7}, status=200) + + client = _async_client(handler) + result = await client.cancel_queued_email_runs() + assert seen["method"] == "POST" + assert seen["path"] == "/agents/inbound-email-status/cancel-queued" + assert result == {"cancelled": 7} + + @pytest.mark.asyncio + async def test_async_resume_inbound_email(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"resumed": True}, status=200) + + client = _async_client(handler) + result = await client.resume_inbound_email() + assert seen["method"] == "POST" + assert seen["path"] == "/agents/inbound-email-status/resume" + assert result == {"resumed": True} + + +class TestEmailDomains: + """Email domains.""" + + def test_list_email_domains(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"domains": [], "can_add_vanity": True}, status=200) + + client = _sync_client(handler) + result = client.list_email_domains() + assert seen["method"] == "GET" + assert seen["path"] == "/email-domains" + assert result == {"domains": [], "can_add_vanity": True} + + def test_add_email_domain(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"id": "d1", "domain": "agent.example.com", "kind": "custom"}, + status=200, + ) + + client = _sync_client(handler) + result = client.add_email_domain( + {"kind": "custom", "value": "agent.example.com", "delegated": True} + ) + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains" + assert seen["body"]["delegated"] is True + assert result == {"id": "d1", "domain": "agent.example.com", "kind": "custom"} + + def test_add_email_domain_vanity_without_delegated(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"id": "d2", "domain": "acme.seclai.com", "kind": "vanity"}, status=200 + ) + + client = _sync_client(handler) + result = client.add_email_domain({"kind": "vanity", "value": "acme"}) + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains" + assert "delegated" not in seen["body"] + assert result == {"id": "d2", "domain": "acme.seclai.com", "kind": "vanity"} + + def test_remove_email_domain(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"removed": True, "cleanup_note": "Delete the NS record"}, status=200 + ) + + client = _sync_client(handler) + result = client.remove_email_domain("d1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/email-domains/d1" + assert result == {"removed": True, "cleanup_note": "Delete the NS record"} + + def test_verify_email_domain(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"id": "d1", "status": "verified", "verified": True}, status=200 + ) + + client = _sync_client(handler) + result = client.verify_email_domain("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/verify" + assert result == {"id": "d1", "status": "verified", "verified": True} + + def test_set_primary_email_domain(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "d1", "is_primary": True}, status=200) + + client = _sync_client(handler) + result = client.set_primary_email_domain("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/primary" + assert result == {"id": "d1", "is_primary": True} + + def test_use_shared_email_domain(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _sync_client(handler) + client.use_shared_email_domain() + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/use-shared-domain" + + def test_send_email_domain_test_email(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"sent": True}, status=200) + + client = _sync_client(handler) + result = client.send_email_domain_test_email("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/test-email" + assert result == {"sent": True} + + def test_get_dmarc_summary(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + {"window_days": 7, "report_count": 2, "total_messages": 100}, status=200 + ) + + client = _sync_client(handler) + result = client.get_dmarc_summary("d1", days=7, top_sources=3) + assert seen["method"] == "GET" + assert seen["path"] == "/email-domains/d1/dmarc" + assert seen["params"] == {"days": "7", "top_sources": "3"} + assert result == {"window_days": 7, "report_count": 2, "total_messages": 100} + + @pytest.mark.asyncio + async def test_async_list_email_domains(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"domains": [], "can_add_vanity": True}, status=200) + + client = _async_client(handler) + result = await client.list_email_domains() + assert seen["method"] == "GET" + assert seen["path"] == "/email-domains" + assert result == {"domains": [], "can_add_vanity": True} + + @pytest.mark.asyncio + async def test_async_add_email_domain(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"id": "d1", "domain": "agent.example.com", "kind": "custom"}, + status=200, + ) + + client = _async_client(handler) + result = await client.add_email_domain( + {"kind": "custom", "value": "agent.example.com", "delegated": True} + ) + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains" + assert seen["body"]["delegated"] is True + assert result == {"id": "d1", "domain": "agent.example.com", "kind": "custom"} + + @pytest.mark.asyncio + async def test_async_add_email_domain_vanity_without_delegated(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["body"] = json.loads(req.content) + return _json_response( + {"id": "d2", "domain": "acme.seclai.com", "kind": "vanity"}, status=200 + ) + + client = _async_client(handler) + result = await client.add_email_domain({"kind": "vanity", "value": "acme"}) + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains" + assert "delegated" not in seen["body"] + assert result == {"id": "d2", "domain": "acme.seclai.com", "kind": "vanity"} + + @pytest.mark.asyncio + async def test_async_remove_email_domain(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"removed": True, "cleanup_note": "Delete the NS record"}, status=200 + ) + + client = _async_client(handler) + result = await client.remove_email_domain("d1") + assert seen["method"] == "DELETE" + assert seen["path"] == "/email-domains/d1" + assert result == {"removed": True, "cleanup_note": "Delete the NS record"} + + @pytest.mark.asyncio + async def test_async_verify_email_domain(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response( + {"id": "d1", "status": "verified", "verified": True}, status=200 + ) + + client = _async_client(handler) + result = await client.verify_email_domain("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/verify" + assert result == {"id": "d1", "status": "verified", "verified": True} + + @pytest.mark.asyncio + async def test_async_set_primary_email_domain(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"id": "d1", "is_primary": True}, status=200) + + client = _async_client(handler) + result = await client.set_primary_email_domain("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/primary" + assert result == {"id": "d1", "is_primary": True} + + @pytest.mark.asyncio + async def test_async_use_shared_email_domain(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response(None, status=204) + + client = _async_client(handler) + await client.use_shared_email_domain() + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/use-shared-domain" + + @pytest.mark.asyncio + async def test_async_send_email_domain_test_email(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"sent": True}, status=200) + + client = _async_client(handler) + result = await client.send_email_domain_test_email("d1") + assert seen["method"] == "POST" + assert seen["path"] == "/email-domains/d1/test-email" + assert result == {"sent": True} + + @pytest.mark.asyncio + async def test_async_get_dmarc_summary(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response( + {"window_days": 7, "report_count": 2, "total_messages": 100}, status=200 + ) + + client = _async_client(handler) + result = await client.get_dmarc_summary("d1", days=7, top_sources=3) + assert seen["method"] == "GET" + assert seen["path"] == "/email-domains/d1/dmarc" + assert seen["params"] == {"days": "7", "top_sources": "3"} + assert result == {"window_days": 7, "report_count": 2, "total_messages": 100} + + +class TestGenerationTiersAndDocsSearch: + """Generation tiers + docs search.""" + + def test_get_generation_tiers(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"image": {"fast": {"model": "m1"}}}, status=200) + + client = _sync_client(handler) + result = client.get_generation_tiers() + assert seen["method"] == "GET" + assert seen["path"] == "/models/generation-tiers" + assert result == {"image": {"fast": {"model": "m1"}}} + + def test_search_docs(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"results": []}, status=200) + + client = _sync_client(handler) + result = client.search_docs("email triggers", mode="semantic", limit=3) + assert seen["method"] == "GET" + assert seen["path"] == "/docs-search" + assert seen["params"] == { + "q": "email triggers", + "mode": "semantic", + "limit": "3", + } + assert result == {"results": []} + + def test_search_docs_omits_unset(self) -> None: + seen: dict[str, Any] = {} + + def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"results": []}, status=200) + + client = _sync_client(handler) + result = client.search_docs("webhooks") + assert seen["method"] == "GET" + assert seen["path"] == "/docs-search" + assert seen["params"] == {"q": "webhooks"} + assert result == {"results": []} + + @pytest.mark.asyncio + async def test_async_get_generation_tiers(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + return _json_response({"image": {"fast": {"model": "m1"}}}, status=200) + + client = _async_client(handler) + result = await client.get_generation_tiers() + assert seen["method"] == "GET" + assert seen["path"] == "/models/generation-tiers" + assert result == {"image": {"fast": {"model": "m1"}}} + + @pytest.mark.asyncio + async def test_async_search_docs(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"results": []}, status=200) + + client = _async_client(handler) + result = await client.search_docs("email triggers", mode="semantic", limit=3) + assert seen["method"] == "GET" + assert seen["path"] == "/docs-search" + assert seen["params"] == { + "q": "email triggers", + "mode": "semantic", + "limit": "3", + } + assert result == {"results": []} + + @pytest.mark.asyncio + async def test_async_search_docs_omits_unset(self) -> None: + seen: dict[str, Any] = {} + + async def handler(req: httpx.Request) -> httpx.Response: + seen["method"] = req.method + seen["path"] = req.url.path + seen["params"] = dict(req.url.params) + return _json_response({"results": []}, status=200) + + client = _async_client(handler) + result = await client.search_docs("webhooks") + assert seen["method"] == "GET" + assert seen["path"] == "/docs-search" + assert seen["params"] == {"q": "webhooks"} + assert result == {"results": []}