cometapi-cli 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
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.
- cometapi_cli/__init__.py +1 -1
- cometapi_cli/client.py +15 -1
- cometapi_cli/commands/logs.py +90 -14
- {cometapi_cli-0.3.1.dist-info → cometapi_cli-0.3.2.dist-info}/METADATA +17 -1
- {cometapi_cli-0.3.1.dist-info → cometapi_cli-0.3.2.dist-info}/RECORD +8 -8
- {cometapi_cli-0.3.1.dist-info → cometapi_cli-0.3.2.dist-info}/WHEEL +1 -1
- {cometapi_cli-0.3.1.dist-info → cometapi_cli-0.3.2.dist-info}/entry_points.txt +0 -0
- {cometapi_cli-0.3.1.dist-info → cometapi_cli-0.3.2.dist-info}/licenses/LICENSE +0 -0
cometapi_cli/__init__.py
CHANGED
cometapi_cli/client.py
CHANGED
|
@@ -9,6 +9,8 @@ import openai
|
|
|
9
9
|
|
|
10
10
|
COMETAPI_BASE_URL = "https://api.cometapi.com/v1"
|
|
11
11
|
COMETAPI_DASHBOARD_BASE = "https://api.cometapi.com"
|
|
12
|
+
ACCOUNT_REQUEST_TIMEOUT = 30.0
|
|
13
|
+
ACCOUNT_EXPORT_TIMEOUT = 120.0
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class CometClient(openai.OpenAI):
|
|
@@ -44,7 +46,14 @@ class CometClient(openai.OpenAI):
|
|
|
44
46
|
|
|
45
47
|
# -- Account management (access-token auth) --------------------------------
|
|
46
48
|
|
|
47
|
-
def _account_request(
|
|
49
|
+
def _account_request(
|
|
50
|
+
self,
|
|
51
|
+
method: str,
|
|
52
|
+
path: str,
|
|
53
|
+
*,
|
|
54
|
+
params: dict | None = None,
|
|
55
|
+
timeout: float = ACCOUNT_REQUEST_TIMEOUT,
|
|
56
|
+
) -> dict:
|
|
48
57
|
"""Make an authenticated request to a CometAPI account endpoint."""
|
|
49
58
|
if not self._access_token:
|
|
50
59
|
raise openai.OpenAIError(
|
|
@@ -56,6 +65,7 @@ class CometClient(openai.OpenAI):
|
|
|
56
65
|
f"{COMETAPI_DASHBOARD_BASE}{path}",
|
|
57
66
|
headers={"Authorization": f"Bearer {self._access_token}"},
|
|
58
67
|
params=params,
|
|
68
|
+
timeout=timeout,
|
|
59
69
|
)
|
|
60
70
|
response.raise_for_status()
|
|
61
71
|
return response.json()
|
|
@@ -151,6 +161,7 @@ class CometClient(openai.OpenAI):
|
|
|
151
161
|
start_timestamp: int | None = None,
|
|
152
162
|
end_timestamp: int | None = None,
|
|
153
163
|
group: str | None = None,
|
|
164
|
+
request_id: str | None = None,
|
|
154
165
|
) -> dict:
|
|
155
166
|
"""List the user's usage logs (requires access token)."""
|
|
156
167
|
params: dict[str, Any] = {"p": page, "page_size": page_size}
|
|
@@ -166,6 +177,8 @@ class CometClient(openai.OpenAI):
|
|
|
166
177
|
params["end_timestamp"] = end_timestamp
|
|
167
178
|
if group:
|
|
168
179
|
params["group"] = group
|
|
180
|
+
if request_id:
|
|
181
|
+
params["request_id"] = request_id
|
|
169
182
|
return self._account_request("GET", "/api/log/self", params=params)
|
|
170
183
|
|
|
171
184
|
def search_logs(self, keyword: str) -> dict:
|
|
@@ -235,6 +248,7 @@ class CometClient(openai.OpenAI):
|
|
|
235
248
|
f"{COMETAPI_DASHBOARD_BASE}/api/log/self/export",
|
|
236
249
|
headers={"Authorization": f"Bearer {self._access_token}"},
|
|
237
250
|
params=params,
|
|
251
|
+
timeout=ACCOUNT_EXPORT_TIMEOUT,
|
|
238
252
|
)
|
|
239
253
|
response.raise_for_status()
|
|
240
254
|
return response.content
|
cometapi_cli/commands/logs.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json as _json
|
|
6
6
|
import sys
|
|
7
|
+
from datetime import datetime, time, timedelta, timezone
|
|
7
8
|
from typing import Annotated
|
|
8
9
|
|
|
9
10
|
import typer
|
|
@@ -31,6 +32,9 @@ LOG_TYPE_MAP = {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
_LOG_TYPE_NAMES = {v: k for k, v in LOG_TYPE_MAP.items()}
|
|
35
|
+
REQUEST_ID_PAGE_SIZE = 100
|
|
36
|
+
REQUEST_ID_DEFAULT_MAX_PAGES = 10
|
|
37
|
+
REQUEST_ID_LOCAL_TZ = timezone(timedelta(hours=8))
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
def _parse_other(other_raw: str | None) -> dict:
|
|
@@ -44,7 +48,29 @@ def _parse_other(other_raw: str | None) -> dict:
|
|
|
44
48
|
return {}
|
|
45
49
|
|
|
46
50
|
|
|
47
|
-
def
|
|
51
|
+
def _request_id_date_window(request_id: str) -> tuple[int, int] | None:
|
|
52
|
+
"""Infer the local request day from a CometAPI request ID prefix."""
|
|
53
|
+
prefix = request_id[:14]
|
|
54
|
+
if len(prefix) != 14 or not prefix.isdigit():
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
local_dt = datetime.strptime(prefix, "%Y%m%d%H%M%S").replace(tzinfo=REQUEST_ID_LOCAL_TZ)
|
|
58
|
+
except ValueError:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
local_day_start = datetime.combine(local_dt.date(), time.min, tzinfo=REQUEST_ID_LOCAL_TZ)
|
|
62
|
+
local_day_end = local_day_start + timedelta(days=1) - timedelta(seconds=1)
|
|
63
|
+
return int(local_day_start.timestamp()), int(local_day_end.timestamp())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _exact_request_id_match(items: list, request_id: str) -> dict | None:
|
|
67
|
+
for item in items:
|
|
68
|
+
if isinstance(item, dict) and item.get("request_id") == request_id:
|
|
69
|
+
return item
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _lookup_log_by_id(
|
|
48
74
|
client: object,
|
|
49
75
|
*,
|
|
50
76
|
request_id: str,
|
|
@@ -54,27 +80,59 @@ def _find_log_by_id(
|
|
|
54
80
|
start_timestamp: int | None = None,
|
|
55
81
|
end_timestamp: int | None = None,
|
|
56
82
|
group: str | None = None,
|
|
57
|
-
max_pages: int =
|
|
58
|
-
) -> dict | None:
|
|
59
|
-
"""
|
|
83
|
+
max_pages: int = REQUEST_ID_DEFAULT_MAX_PAGES,
|
|
84
|
+
) -> tuple[dict | None, dict]:
|
|
85
|
+
"""Look up a log entry by request ID, using server filtering before fallback scans."""
|
|
86
|
+
meta = {
|
|
87
|
+
"used_server_filter": False,
|
|
88
|
+
"used_inferred_window": False,
|
|
89
|
+
"scanned_pages": 0,
|
|
90
|
+
"max_pages": max_pages,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Older backends may ignore request_id, so verify the returned item matches
|
|
94
|
+
# before trusting this direct lookup.
|
|
95
|
+
resp = client.list_logs( # type: ignore[union-attr]
|
|
96
|
+
page=1,
|
|
97
|
+
page_size=REQUEST_ID_PAGE_SIZE,
|
|
98
|
+
log_type=log_type,
|
|
99
|
+
model_name=model_name,
|
|
100
|
+
token_name=token_name,
|
|
101
|
+
start_timestamp=start_timestamp,
|
|
102
|
+
end_timestamp=end_timestamp,
|
|
103
|
+
group=group,
|
|
104
|
+
request_id=request_id,
|
|
105
|
+
)
|
|
106
|
+
meta["used_server_filter"] = True
|
|
107
|
+
if match := _exact_request_id_match(extract_items(resp), request_id):
|
|
108
|
+
return match, meta
|
|
109
|
+
|
|
110
|
+
scan_start = start_timestamp
|
|
111
|
+
scan_end = end_timestamp
|
|
112
|
+
if scan_start is None and scan_end is None:
|
|
113
|
+
inferred = _request_id_date_window(request_id)
|
|
114
|
+
if inferred:
|
|
115
|
+
scan_start, scan_end = inferred
|
|
116
|
+
meta["used_inferred_window"] = True
|
|
117
|
+
|
|
60
118
|
for pg in range(1, max_pages + 1):
|
|
61
119
|
resp = client.list_logs( # type: ignore[union-attr]
|
|
62
120
|
page=pg,
|
|
63
|
-
page_size=
|
|
121
|
+
page_size=REQUEST_ID_PAGE_SIZE,
|
|
64
122
|
log_type=log_type,
|
|
65
123
|
model_name=model_name,
|
|
66
124
|
token_name=token_name,
|
|
67
|
-
start_timestamp=
|
|
68
|
-
end_timestamp=
|
|
125
|
+
start_timestamp=scan_start,
|
|
126
|
+
end_timestamp=scan_end,
|
|
69
127
|
group=group,
|
|
70
128
|
)
|
|
129
|
+
meta["scanned_pages"] = pg
|
|
71
130
|
items = extract_items(resp)
|
|
72
131
|
if not items:
|
|
73
132
|
break
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return None
|
|
133
|
+
if match := _exact_request_id_match(items, request_id):
|
|
134
|
+
return match, meta
|
|
135
|
+
return None, meta
|
|
78
136
|
|
|
79
137
|
|
|
80
138
|
def _build_log_record(log: dict) -> dict:
|
|
@@ -226,6 +284,14 @@ def logs(
|
|
|
226
284
|
str | None,
|
|
227
285
|
typer.Option("--request-id", help="Look up cost by request ID (X-Cometapi-Request-Id header)."),
|
|
228
286
|
] = None,
|
|
287
|
+
request_id_max_pages: Annotated[
|
|
288
|
+
int,
|
|
289
|
+
typer.Option(
|
|
290
|
+
"--request-id-max-pages",
|
|
291
|
+
min=1,
|
|
292
|
+
help="Fallback pages to scan when direct request-ID lookup is unavailable.",
|
|
293
|
+
),
|
|
294
|
+
] = REQUEST_ID_DEFAULT_MAX_PAGES,
|
|
229
295
|
detail: Annotated[
|
|
230
296
|
bool,
|
|
231
297
|
typer.Option("--detail", help="Show extended columns (request ID, pricing ratios)."),
|
|
@@ -259,7 +325,7 @@ def logs(
|
|
|
259
325
|
if log_type:
|
|
260
326
|
type_int = LOG_TYPE_MAP.get(log_type.lower())
|
|
261
327
|
|
|
262
|
-
log_entry =
|
|
328
|
+
log_entry, lookup_meta = _lookup_log_by_id(
|
|
263
329
|
client,
|
|
264
330
|
request_id=request_id,
|
|
265
331
|
log_type=type_int,
|
|
@@ -268,13 +334,22 @@ def logs(
|
|
|
268
334
|
start_timestamp=start_ts,
|
|
269
335
|
end_timestamp=end_ts,
|
|
270
336
|
group=group,
|
|
337
|
+
max_pages=request_id_max_pages,
|
|
271
338
|
)
|
|
272
339
|
|
|
273
340
|
if log_entry is None:
|
|
341
|
+
if lookup_meta["used_inferred_window"]:
|
|
342
|
+
scan_note = (
|
|
343
|
+
f"Then scanned {lookup_meta['scanned_pages']} page(s) in the request ID's "
|
|
344
|
+
"inferred local date window."
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
scan_note = f"Then scanned {lookup_meta['scanned_pages']} fallback page(s)."
|
|
274
348
|
err_console.print(
|
|
275
349
|
f"[red]No log found for request_id=[/]{request_id}\n"
|
|
276
|
-
"[dim]
|
|
277
|
-
"
|
|
350
|
+
"[dim]Tried direct request-id lookup first. "
|
|
351
|
+
f"{scan_note} Increase --request-id-max-pages or narrow with --start/--end "
|
|
352
|
+
"if the backend does not support direct request-id filtering.[/]"
|
|
278
353
|
)
|
|
279
354
|
raise typer.Exit(code=1)
|
|
280
355
|
|
|
@@ -324,6 +399,7 @@ def logs(
|
|
|
324
399
|
start_timestamp=start_ts,
|
|
325
400
|
end_timestamp=end_ts,
|
|
326
401
|
group=group,
|
|
402
|
+
request_id=None,
|
|
327
403
|
)
|
|
328
404
|
|
|
329
405
|
data = extract_items(resp)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cometapi-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: CometAPI CLI — official command-line interface for the CometAPI AI gateway
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/cometapi-cli/
|
|
6
6
|
Project-URL: Documentation, https://apidoc.cometapi.com/libraries/cli/overview
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Internet
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: click>=8.0
|
|
24
25
|
Requires-Dist: openai>=1.0.0
|
|
25
26
|
Requires-Dist: prompt-toolkit>=3.0
|
|
26
27
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -144,6 +145,21 @@ cometapi run -h
|
|
|
144
145
|
| `repl` | Start an interactive command shell | Depends on command used |
|
|
145
146
|
| `config` | Show, set, unset, or locate local configuration | None |
|
|
146
147
|
|
|
148
|
+
## Logs
|
|
149
|
+
|
|
150
|
+
Use `cometapi logs` to inspect recent usage, export CSV, or look up one request by the
|
|
151
|
+
`X-Cometapi-Request-Id` response header.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cometapi logs --limit 20
|
|
155
|
+
cometapi logs --type consume --start 2026-06-01 --json
|
|
156
|
+
cometapi logs --request-id 20260617165550885561292gJBlzjtp
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`logs --request-id` first asks the backend for that exact request ID. If the installed
|
|
160
|
+
backend does not support direct request-ID filtering yet, the CLI falls back to scanning
|
|
161
|
+
a bounded number of log pages. Use `--request-id-max-pages` to widen that fallback scan.
|
|
162
|
+
|
|
147
163
|
## Models
|
|
148
164
|
|
|
149
165
|
`cometapi models` uses the public model catalog by default and displays richer metadata than `/v1/models`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
cometapi_cli/__init__.py,sha256=
|
|
1
|
+
cometapi_cli/__init__.py,sha256=I37ML-zws7ZAjDJSAw87LxYgHMsTcaVOz67of_juqiI,92
|
|
2
2
|
cometapi_cli/app.py,sha256=d2IZJabWmdZtPtFNOB4YiuSJ1GYvO0hgIOkjm4gQjxA,2948
|
|
3
3
|
cometapi_cli/catalog.py,sha256=Pc0XGoskMEKIzfbU2H_0Yi8zecLzKl1ss8fd2mV7778,4929
|
|
4
|
-
cometapi_cli/client.py,sha256=
|
|
4
|
+
cometapi_cli/client.py,sha256=QBtQ1cvmwvwjpIYpzupC3BMNqxREhMoRnzgCZOeJnEE,9998
|
|
5
5
|
cometapi_cli/config.py,sha256=oJXQidKCOsKNYPnE8OfLLoOfsv0MSZEDICB6VShJRSA,3307
|
|
6
6
|
cometapi_cli/console.py,sha256=HFSU1gL9SDmwjBvgVgDraoaU50oTwCRpsAwYXYlVP9o,163
|
|
7
7
|
cometapi_cli/constants.py,sha256=UPfU36fnRTl4JdcyjySQ0UEVign3i_UTNH9mQ-Gb4ls,1826
|
|
@@ -16,7 +16,7 @@ cometapi_cli/commands/chat.py,sha256=xQgsjFQ33kVRfSnU5t3fbahjZE8nmVSa_w8qUTPeacE
|
|
|
16
16
|
cometapi_cli/commands/chat_repl.py,sha256=b9lkYnbbaOb0AlSpRcq3wWHmnhtTXvaqeJiUImsMEBY,8176
|
|
17
17
|
cometapi_cli/commands/config_cmd.py,sha256=OqR0TuAq6gQXw8nJIt5AAv_Fk4zdfbg-KhNLuUsrEsU,8619
|
|
18
18
|
cometapi_cli/commands/doctor.py,sha256=s9XzRIkF9FIMAWSc7b3c6v3g8Px9kb0k6WDucOrdXY8,5360
|
|
19
|
-
cometapi_cli/commands/logs.py,sha256=
|
|
19
|
+
cometapi_cli/commands/logs.py,sha256=__6Ft5TpBwVtRPTkFbmCjnriGfb1CBNGgGH_CWFmOeY,16082
|
|
20
20
|
cometapi_cli/commands/model.py,sha256=YyMRt6enCAZfxW_dVcNvdNgYSl-J4iOqPlSsv14nfJs,2654
|
|
21
21
|
cometapi_cli/commands/models.py,sha256=1TQC8LJ6JASErkLG5XtaZyRrq4vr4sVWH7eo426FeNs,8824
|
|
22
22
|
cometapi_cli/commands/repl.py,sha256=b5z1jmEXOsCrb6fwEUyv-IKot929NIPQQAbA1uas1D4,4283
|
|
@@ -24,8 +24,8 @@ cometapi_cli/commands/run.py,sha256=JQQ1DFSyJoD4pByLmhMht1-XhDDZpV94F0hshXL7hKY,
|
|
|
24
24
|
cometapi_cli/commands/stats.py,sha256=bEVywsom7bm8AGKGWlgAWTFjOp4NTJxDk6YEEvECM6U,1411
|
|
25
25
|
cometapi_cli/commands/tasks.py,sha256=NBKOKrDow52sVjoM0ezwS9IhVRltlqYLmpamoEUbAWA,4718
|
|
26
26
|
cometapi_cli/commands/tokens.py,sha256=U0AI8T690NJxAKFBwrWrsr3izXIiB_PZ9avmwpdgM9A,3042
|
|
27
|
-
cometapi_cli-0.3.
|
|
28
|
-
cometapi_cli-0.3.
|
|
29
|
-
cometapi_cli-0.3.
|
|
30
|
-
cometapi_cli-0.3.
|
|
31
|
-
cometapi_cli-0.3.
|
|
27
|
+
cometapi_cli-0.3.2.dist-info/METADATA,sha256=WJ4GHqBwefcOBQJ4A5jF3vLTcqotUOUmd4pAy0dld_s,10646
|
|
28
|
+
cometapi_cli-0.3.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
29
|
+
cometapi_cli-0.3.2.dist-info/entry_points.txt,sha256=xoiE2ZVNNWXTq0JRBtzC8hJ2JkdKuaBNz_fEd8OJpLs,50
|
|
30
|
+
cometapi_cli-0.3.2.dist-info/licenses/LICENSE,sha256=-rBwHQzkmLbty07abmGvQvsRrvDeEQUkPDhNJfTcjdE,1065
|
|
31
|
+
cometapi_cli-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|