ndi-cli 0.8.0__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/PKG-INFO +23 -6
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/README.md +21 -4
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/pyproject.toml +2 -2
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/pyproject.toml.orig +2 -2
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/__init__.py +1 -1
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_cli.py +10 -8
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_jobs.py +26 -9
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_local.py +97 -10
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_render.py +329 -3
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_sources.py +69 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_tools.py +171 -2
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_workspace.py +306 -9
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/commands.py +8 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/LICENSE +0 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_config.py +0 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_docops.py +0 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_entrypoint.py +0 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_login.py +0 -0
- {ndi_cli-0.8.0 → ndi_cli-0.10.0}/src/ndi_cli/_output.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools
|
|
5
5
|
Keywords: ndi,cli,document-intelligence,coding-agents
|
|
6
6
|
Author: Nace AI
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
|
-
Requires-Dist: ndi-sdk>=0.
|
|
19
|
+
Requires-Dist: ndi-sdk>=0.25,<1
|
|
20
20
|
Requires-Dist: truststore>=0.10.4,<1
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Project-URL: Homepage, https://ndi-api.nace.ai
|
|
@@ -50,14 +50,15 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
50
50
|
| `ndi extract SOURCE -s SCHEMA` | Structured extract (`--validate` checks a schema with no job) |
|
|
51
51
|
| `ndi split SOURCE --class id:label` | Logical sections |
|
|
52
52
|
| `ndi classify SOURCE --class id:label` | Labels (refuses `jobid://`) |
|
|
53
|
-
| `ndi ground SOURCE --target id=TEXT` | Locate quoted text |
|
|
53
|
+
| `ndi ground SOURCE --target id=TEXT` | Locate quoted text (standalone quotes only; after search, poll `grounding_job_ids`) |
|
|
54
54
|
| `ndi job ID` / `ndi jobs` / `ndi cancel ID` | Inspect or cancel jobs |
|
|
55
55
|
| `ndi workspace create\|list\|get\|stats\|delete\|use` | Workspace lifecycle |
|
|
56
|
-
| `ndi files upload\|list\|get\|delete` | Workspace files (`--ingest`
|
|
56
|
+
| `ndi files upload\|list\|get\|delete` | Workspace files (a directory walks nested files; `--ingest` then queues ingestion) |
|
|
57
57
|
| `ndi ingest` | Queue ingestion |
|
|
58
|
-
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search |
|
|
58
|
+
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search; then `ndi job` on each printed `grounding_job_ids` entry |
|
|
59
59
|
| `ndi filtered-search QUERY` | Corpus filter/rank/count over the metadata catalog (`--cursor` pages an earlier run) |
|
|
60
|
-
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--effort` for the
|
|
60
|
+
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--reasoning-effort` for the thinking budget) |
|
|
61
|
+
| `ndi ledger-understanding start\|get\|sources\|select\|selection` | Measure the ledger package, confirm sources, or read the latest report |
|
|
61
62
|
| `ndi folder-metadata` / `file-metadata` / `read-file` / `ask-file` / `run-sql` / `hybrid-search` | Read-only workspace tools (the sandbox surface) |
|
|
62
63
|
|
|
63
64
|
## Sources
|
|
@@ -74,8 +75,19 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
74
75
|
|
|
75
76
|
```bash
|
|
76
77
|
ndi parse a.pdf -o id | ndi extract - -s schema.json
|
|
78
|
+
ndi files upload ./corpus --ingest
|
|
79
|
+
ndi ledger-understanding start # --force to re-scan; --selection-id after confirm
|
|
80
|
+
ndi ledger-understanding get
|
|
81
|
+
ndi ledger-understanding sources --review-reading-id READING
|
|
82
|
+
ndi ledger-understanding select --primary SRC --supporting SRC --revision REV
|
|
77
83
|
```
|
|
78
84
|
|
|
85
|
+
`ndi files upload DIR` keeps relative paths under `--path` (default: the
|
|
86
|
+
directory name) and uploads in parallel (`-j`, default 8). Unsupported
|
|
87
|
+
files (and `.DS_Store`) are skipped; a warning on stderr lists them when
|
|
88
|
+
the run finishes. `--ingest` then queues ingestion in batches of 1000
|
|
89
|
+
file ids. A single file still requires `--path`.
|
|
90
|
+
|
|
79
91
|
## Output
|
|
80
92
|
|
|
81
93
|
Result content goes to **stdout**; status (`job <id> queued`, `saved …`) goes
|
|
@@ -94,6 +106,11 @@ URLs automatically.
|
|
|
94
106
|
API failures exit 1. Usage / config errors exit 2. Schema-validation
|
|
95
107
|
failures also show up to five field constraints, each limited to 500
|
|
96
108
|
characters; request input and unrelated error-detail fields are omitted.
|
|
109
|
+
Unsupported inputs print `error: 422 [unsupported_file_type] ...` to stderr and
|
|
110
|
+
exit 1. `ndi upload` prints no handle, document operations print no job ID, and
|
|
111
|
+
`ndi files upload --ingest` creates no workspace file or ingestion job.
|
|
112
|
+
The server checks a bounded byte prefix, rejects conclusive filename/content
|
|
113
|
+
conflicts, and treats the multipart `Content-Type` as advisory.
|
|
97
114
|
|
|
98
115
|
See the [CLI guide](https://docs.ndi.nace.ai/guides/cli) for the full command
|
|
99
116
|
reference. `SKILL.md` is the agent-facing guide to the six workspace tools.
|
|
@@ -26,14 +26,15 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
26
26
|
| `ndi extract SOURCE -s SCHEMA` | Structured extract (`--validate` checks a schema with no job) |
|
|
27
27
|
| `ndi split SOURCE --class id:label` | Logical sections |
|
|
28
28
|
| `ndi classify SOURCE --class id:label` | Labels (refuses `jobid://`) |
|
|
29
|
-
| `ndi ground SOURCE --target id=TEXT` | Locate quoted text |
|
|
29
|
+
| `ndi ground SOURCE --target id=TEXT` | Locate quoted text (standalone quotes only; after search, poll `grounding_job_ids`) |
|
|
30
30
|
| `ndi job ID` / `ndi jobs` / `ndi cancel ID` | Inspect or cancel jobs |
|
|
31
31
|
| `ndi workspace create\|list\|get\|stats\|delete\|use` | Workspace lifecycle |
|
|
32
|
-
| `ndi files upload\|list\|get\|delete` | Workspace files (`--ingest`
|
|
32
|
+
| `ndi files upload\|list\|get\|delete` | Workspace files (a directory walks nested files; `--ingest` then queues ingestion) |
|
|
33
33
|
| `ndi ingest` | Queue ingestion |
|
|
34
|
-
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search |
|
|
34
|
+
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search; then `ndi job` on each printed `grounding_job_ids` entry |
|
|
35
35
|
| `ndi filtered-search QUERY` | Corpus filter/rank/count over the metadata catalog (`--cursor` pages an earlier run) |
|
|
36
|
-
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--effort` for the
|
|
36
|
+
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--reasoning-effort` for the thinking budget) |
|
|
37
|
+
| `ndi ledger-understanding start\|get\|sources\|select\|selection` | Measure the ledger package, confirm sources, or read the latest report |
|
|
37
38
|
| `ndi folder-metadata` / `file-metadata` / `read-file` / `ask-file` / `run-sql` / `hybrid-search` | Read-only workspace tools (the sandbox surface) |
|
|
38
39
|
|
|
39
40
|
## Sources
|
|
@@ -50,8 +51,19 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
53
|
ndi parse a.pdf -o id | ndi extract - -s schema.json
|
|
54
|
+
ndi files upload ./corpus --ingest
|
|
55
|
+
ndi ledger-understanding start # --force to re-scan; --selection-id after confirm
|
|
56
|
+
ndi ledger-understanding get
|
|
57
|
+
ndi ledger-understanding sources --review-reading-id READING
|
|
58
|
+
ndi ledger-understanding select --primary SRC --supporting SRC --revision REV
|
|
53
59
|
```
|
|
54
60
|
|
|
61
|
+
`ndi files upload DIR` keeps relative paths under `--path` (default: the
|
|
62
|
+
directory name) and uploads in parallel (`-j`, default 8). Unsupported
|
|
63
|
+
files (and `.DS_Store`) are skipped; a warning on stderr lists them when
|
|
64
|
+
the run finishes. `--ingest` then queues ingestion in batches of 1000
|
|
65
|
+
file ids. A single file still requires `--path`.
|
|
66
|
+
|
|
55
67
|
## Output
|
|
56
68
|
|
|
57
69
|
Result content goes to **stdout**; status (`job <id> queued`, `saved …`) goes
|
|
@@ -70,6 +82,11 @@ URLs automatically.
|
|
|
70
82
|
API failures exit 1. Usage / config errors exit 2. Schema-validation
|
|
71
83
|
failures also show up to five field constraints, each limited to 500
|
|
72
84
|
characters; request input and unrelated error-detail fields are omitted.
|
|
85
|
+
Unsupported inputs print `error: 422 [unsupported_file_type] ...` to stderr and
|
|
86
|
+
exit 1. `ndi upload` prints no handle, document operations print no job ID, and
|
|
87
|
+
`ndi files upload --ingest` creates no workspace file or ingestion job.
|
|
88
|
+
The server checks a bounded byte prefix, rejects conclusive filename/content
|
|
89
|
+
conflicts, and treats the multipart `Content-Type` as advisory.
|
|
73
90
|
|
|
74
91
|
See the [CLI guide](https://docs.ndi.nace.ai/guides/cli) for the full command
|
|
75
92
|
reference. `SKILL.md` is the agent-facing guide to the six workspace tools.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ndi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Typing :: Typed",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"ndi-sdk>=0.
|
|
27
|
+
"ndi-sdk>=0.25,<1",
|
|
28
28
|
"truststore>=0.10.4,<1",
|
|
29
29
|
]
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ndi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Nace AI", email = "engineering@nace.ai" }
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Typing :: Typed",
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
|
-
"ndi-sdk>=0.
|
|
30
|
+
"ndi-sdk>=0.25,<1",
|
|
31
31
|
"truststore>=0.10.4,<1",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -16,7 +16,7 @@ from ndi_sdk.resources.tools import SyncTools
|
|
|
16
16
|
|
|
17
17
|
from ndi_cli import _docops, _jobs, _login, _tools, _workspace
|
|
18
18
|
from ndi_cli._config import ConfigError, resolve_settings
|
|
19
|
-
from ndi_cli._local import SOCKET_ENV, TOKEN_ENV, LocalTransport
|
|
19
|
+
from ndi_cli._local import KG_ENV, SOCKET_ENV, TOKEN_ENV, LocalTransport
|
|
20
20
|
from ndi_cli._tools import parse_pages
|
|
21
21
|
|
|
22
22
|
# qa-file runs a durable workflow that legitimately takes minutes; the metadata,
|
|
@@ -27,16 +27,18 @@ _VALIDATION_ERROR_CHARS = 500
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def main(argv: list[str] | None = None) -> int:
|
|
30
|
-
|
|
30
|
+
socket_path = os.environ.get(SOCKET_ENV)
|
|
31
|
+
# Presence, not truthiness: an empty $NDI_CLI_SOCKET still means "inside an
|
|
32
|
+
# agent run", and must not silently unlock the full platform CLI.
|
|
33
|
+
in_agent_run = socket_path is not None
|
|
34
|
+
# An agent run gets the graph commands only when the worker granted them; a
|
|
35
|
+
# run whose guide never mentions kg-* must not find them in --help either.
|
|
36
|
+
parser = _build_parser(graph=not in_agent_run or bool(os.environ.get(KG_ENV)))
|
|
31
37
|
args = parser.parse_args(argv)
|
|
32
38
|
if args.command is None:
|
|
33
39
|
parser.print_help()
|
|
34
40
|
return 2
|
|
35
41
|
family = getattr(args, "family", None)
|
|
36
|
-
socket_path = os.environ.get(SOCKET_ENV)
|
|
37
|
-
# Presence, not truthiness: an empty $NDI_CLI_SOCKET still means "inside an
|
|
38
|
-
# agent run", and must not silently unlock the full platform CLI.
|
|
39
|
-
in_agent_run = socket_path is not None
|
|
40
42
|
workspace_flag = getattr(args, "workspace", None)
|
|
41
43
|
if workspace_flag is not None and not workspace_flag.strip():
|
|
42
44
|
print("error: --workspace needs a workspace id", file=sys.stderr)
|
|
@@ -130,7 +132,7 @@ def _validation_error_lines(detail: object) -> list[str]:
|
|
|
130
132
|
return lines
|
|
131
133
|
|
|
132
134
|
|
|
133
|
-
def _build_parser() -> argparse.ArgumentParser:
|
|
135
|
+
def _build_parser(*, graph: bool = True) -> argparse.ArgumentParser:
|
|
134
136
|
parser = argparse.ArgumentParser(
|
|
135
137
|
prog="ndi",
|
|
136
138
|
description=(
|
|
@@ -151,7 +153,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
151
153
|
)
|
|
152
154
|
sub = parser.add_subparsers(dest="command")
|
|
153
155
|
_login.add_parsers(sub)
|
|
154
|
-
_tools.add_parsers(sub, common)
|
|
156
|
+
_tools.add_parsers(sub, common, graph=graph)
|
|
155
157
|
_docops.add_parsers(sub, common)
|
|
156
158
|
_jobs.add_parsers(sub, common)
|
|
157
159
|
_workspace.add_parsers(sub, common)
|
|
@@ -36,18 +36,17 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
|
|
|
36
36
|
cancel.set_defaults(run=_run_cancel, family="job", needs_workspace=False, needs_client=True)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def wait_job(
|
|
40
40
|
client: NdiClient,
|
|
41
41
|
job: Job,
|
|
42
42
|
args: argparse.Namespace,
|
|
43
43
|
*,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
print_timeout_id: bool = True,
|
|
45
|
+
) -> tuple[Job | None, int]:
|
|
46
|
+
"""Wait for ``job``; status lines go to stderr. Does not render the result."""
|
|
47
47
|
print(f"job {job.job_id} {job.status}", file=sys.stderr)
|
|
48
48
|
if getattr(args, "async_submit", False):
|
|
49
|
-
|
|
50
|
-
return 0
|
|
49
|
+
return job, 0
|
|
51
50
|
try:
|
|
52
51
|
if job.is_terminal:
|
|
53
52
|
check_terminal(job, raise_on_failure=True)
|
|
@@ -56,12 +55,30 @@ def wait_and_report(
|
|
|
56
55
|
except JobFailedError as exc:
|
|
57
56
|
code = f" [{exc.job.error.code}]" if exc.job.error and exc.job.error.code else ""
|
|
58
57
|
print(f"error:{code} {exc}", file=sys.stderr)
|
|
59
|
-
return 1
|
|
58
|
+
return exc.job, 1
|
|
60
59
|
except JobTimeoutError as exc:
|
|
61
60
|
print(f"error: {exc}", file=sys.stderr)
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if print_timeout_id:
|
|
62
|
+
print(exc.job.job_id)
|
|
63
|
+
return exc.job, 1
|
|
64
64
|
print(f"job {job.job_id} {job.status}", file=sys.stderr)
|
|
65
|
+
return job, 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def wait_and_report(
|
|
69
|
+
client: NdiClient,
|
|
70
|
+
job: Job,
|
|
71
|
+
args: argparse.Namespace,
|
|
72
|
+
*,
|
|
73
|
+
auto: Callable[[Job], str],
|
|
74
|
+
stem: str,
|
|
75
|
+
) -> int:
|
|
76
|
+
job, code = wait_job(client, job, args)
|
|
77
|
+
if code != 0 or job is None:
|
|
78
|
+
return code or 1
|
|
79
|
+
if getattr(args, "async_submit", False):
|
|
80
|
+
print(job.job_id)
|
|
81
|
+
return 0
|
|
65
82
|
return render_job(job, args, auto=auto, stem=stem)
|
|
66
83
|
|
|
67
84
|
|
|
@@ -17,6 +17,8 @@ from ndi_sdk.errors import ErrorBody, NdiConnectionError, NdiTimeoutError, error
|
|
|
17
17
|
from ndi_sdk.models.tools import (
|
|
18
18
|
FileMetadataRequest,
|
|
19
19
|
FolderMetadataRequest,
|
|
20
|
+
KgSearchRequest,
|
|
21
|
+
KgWalkRequest,
|
|
20
22
|
QaFileRequest,
|
|
21
23
|
ReadFileRequest,
|
|
22
24
|
RunSqlRequest,
|
|
@@ -28,6 +30,9 @@ from ndi_cli.commands import WorkspaceCommand
|
|
|
28
30
|
|
|
29
31
|
SOCKET_ENV = "NDI_CLI_SOCKET"
|
|
30
32
|
TOKEN_ENV = "NDI_CLI_TOKEN"
|
|
33
|
+
#: Set (to any non-empty value) by the worker for an agent run that has the graph
|
|
34
|
+
#: surface; inside an agent run the ``kg-*`` commands are registered only when it is.
|
|
35
|
+
KG_ENV = "NDI_CLI_KG"
|
|
31
36
|
MAX_MESSAGE_BYTES = 16 * 1024 * 1024
|
|
32
37
|
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
33
38
|
|
|
@@ -62,12 +67,102 @@ class SearchCommand(BaseModel):
|
|
|
62
67
|
body: SearchRequest
|
|
63
68
|
|
|
64
69
|
|
|
70
|
+
class KgInfoRequest(BaseModel):
|
|
71
|
+
"""``kg-info``'s scope as a socket body.
|
|
72
|
+
|
|
73
|
+
Defined here rather than imported from ``ndi_sdk.models.tools`` like its
|
|
74
|
+
siblings: on the wire ``kg-info`` is a GET carrying query parameters, so it has
|
|
75
|
+
no request model to reuse. Only the scope crosses — the drawable sample is for
|
|
76
|
+
a client that renders a graph, and ``_kg_info_command`` refuses it.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
path_prefix: str | None = None
|
|
80
|
+
hub_node_type: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class KgInfoCommand(BaseModel):
|
|
84
|
+
command: Literal[WorkspaceCommand.KG_INFO] = WorkspaceCommand.KG_INFO
|
|
85
|
+
body: KgInfoRequest
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class KgSearchCommand(BaseModel):
|
|
89
|
+
command: Literal[WorkspaceCommand.KG_SEARCH] = WorkspaceCommand.KG_SEARCH
|
|
90
|
+
body: KgSearchRequest
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class KgWalkCommand(BaseModel):
|
|
94
|
+
command: Literal[WorkspaceCommand.KG_WALK] = WorkspaceCommand.KG_WALK
|
|
95
|
+
body: KgWalkRequest
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class KgAnswerRequest(BaseModel):
|
|
99
|
+
"""``kg-answer``'s question as a socket body.
|
|
100
|
+
|
|
101
|
+
The agent passes its verbatim question; the server runs the graph pass and
|
|
102
|
+
returns the rendered outcome (answered text + citations, or evidence + gap).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
query: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class KgAnswerResponse(BaseModel):
|
|
109
|
+
"""The rendered outcome of one graph pass, ready to print or inject as a tool result."""
|
|
110
|
+
|
|
111
|
+
text: str
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class KgAnswerCommand(BaseModel):
|
|
115
|
+
command: Literal[WorkspaceCommand.KG_ANSWER] = WorkspaceCommand.KG_ANSWER
|
|
116
|
+
body: KgAnswerRequest
|
|
117
|
+
|
|
118
|
+
|
|
65
119
|
Command = Annotated[
|
|
66
|
-
FolderCommand
|
|
120
|
+
FolderCommand
|
|
121
|
+
| FileCommand
|
|
122
|
+
| ReadCommand
|
|
123
|
+
| AskCommand
|
|
124
|
+
| SqlCommand
|
|
125
|
+
| SearchCommand
|
|
126
|
+
| KgInfoCommand
|
|
127
|
+
| KgSearchCommand
|
|
128
|
+
| KgWalkCommand
|
|
129
|
+
| KgAnswerCommand,
|
|
130
|
+
Field(discriminator="command"),
|
|
67
131
|
]
|
|
68
132
|
COMMAND = TypeAdapter(Command)
|
|
69
133
|
|
|
70
134
|
|
|
135
|
+
def _kg_info_command(params: object) -> Command:
|
|
136
|
+
"""Build the ``kg-info`` command from the SDK method's query parameters.
|
|
137
|
+
|
|
138
|
+
Refuses ``include_graph`` instead of dropping it: the sample is the whole point
|
|
139
|
+
of that flag, and answering without one would read as a graph with no structure.
|
|
140
|
+
"""
|
|
141
|
+
if not isinstance(params, dict):
|
|
142
|
+
raise ValueError("The internal CLI accepts workspace commands only.")
|
|
143
|
+
if params.get("include_graph"):
|
|
144
|
+
raise ValueError("The internal CLI does not serve kg-info's drawable graph sample.")
|
|
145
|
+
prefix = params.get("path_prefix")
|
|
146
|
+
if prefix is not None and not isinstance(prefix, str):
|
|
147
|
+
raise ValueError("kg-info's path_prefix must be a string.")
|
|
148
|
+
hub_node_type = params.get("hub_node_type")
|
|
149
|
+
if hub_node_type is not None and not isinstance(hub_node_type, str):
|
|
150
|
+
raise ValueError("kg-info's hub_node_type must be a string.")
|
|
151
|
+
return KgInfoCommand(body=KgInfoRequest(path_prefix=prefix, hub_node_type=hub_node_type))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _command(method: str, path: str, kwargs: dict[str, object]) -> Command:
|
|
155
|
+
"""One socket command per SDK call, named by the endpoint the method addresses."""
|
|
156
|
+
name = path.rsplit("/", 1)[-1]
|
|
157
|
+
if method == "GET" and name == WorkspaceCommand.KG_INFO:
|
|
158
|
+
return _kg_info_command(kwargs.get("params"))
|
|
159
|
+
body = kwargs.get("body")
|
|
160
|
+
if method != "POST" or not isinstance(body, BaseModel):
|
|
161
|
+
raise ValueError("The internal CLI accepts workspace commands only.")
|
|
162
|
+
command = WorkspaceCommand.ASK_FILE if name == "qa-file" else WorkspaceCommand(name)
|
|
163
|
+
return COMMAND.validate_python({"command": command, "body": body.model_dump(exclude_none=True)})
|
|
164
|
+
|
|
165
|
+
|
|
71
166
|
class CommandEnvelope(BaseModel):
|
|
72
167
|
"""One request line on the socket: the owning run's token plus its command."""
|
|
73
168
|
|
|
@@ -98,15 +193,7 @@ class LocalTransport:
|
|
|
98
193
|
self.token = token
|
|
99
194
|
|
|
100
195
|
def request_model(self, model: type[ModelT], method: str, path: str, **kwargs: object) -> ModelT:
|
|
101
|
-
|
|
102
|
-
if method != "POST" or not isinstance(body, BaseModel):
|
|
103
|
-
raise ValueError("The internal CLI accepts workspace commands only.")
|
|
104
|
-
name = path.rsplit("/", 1)[-1]
|
|
105
|
-
command = WorkspaceCommand.ASK_FILE if name == "qa-file" else WorkspaceCommand(name)
|
|
106
|
-
request = CommandEnvelope(
|
|
107
|
-
token=self.token,
|
|
108
|
-
command=COMMAND.validate_python({"command": command, "body": body.model_dump(exclude_none=True)}),
|
|
109
|
-
)
|
|
196
|
+
request = CommandEnvelope(token=self.token, command=_command(method, path, kwargs))
|
|
110
197
|
try:
|
|
111
198
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
|
|
112
199
|
connection.settimeout(self.timeout)
|