gemx 0.2.4__tar.gz → 0.2.5__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.
- {gemx-0.2.4 → gemx-0.2.5}/PKG-INFO +7 -8
- {gemx-0.2.4 → gemx-0.2.5}/README.md +6 -7
- {gemx-0.2.4 → gemx-0.2.5}/pyproject.toml +1 -1
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/__init__.py +1 -1
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/client.py +11 -14
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/formats.py +14 -5
- {gemx-0.2.4 → gemx-0.2.5}/.gitignore +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/LICENSE +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/cli.py +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/errors.py +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/src/gemx/py.typed +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/__init__.py +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/scripts/TEST_GEMINI.md +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/scripts/gemini_console.js +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/scripts/gemini_console_prompt.js +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/test_cli.py +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/test_client.py +0 -0
- {gemx-0.2.4 → gemx-0.2.5}/tests/test_formats.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gemx
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Drive the Gemini web UI from Python via Playwright
|
|
5
5
|
Project-URL: Homepage, https://github.com/ra0x3/gemx
|
|
6
6
|
Project-URL: Repository, https://github.com/ra0x3/gemx
|
|
@@ -26,13 +26,12 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
`gemini.google.com` as if it were an API, using Playwright to enter a prompt,
|
|
27
27
|
submit it, and capture the structured reply.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*peak* streamed text because Gemini re-mounts the response node mid-stream.
|
|
29
|
+
Gemx enters prompts using the input routine verified by the browser-console
|
|
30
|
+
recovery script: it replaces the editor contents with a paragraph and dispatches
|
|
31
|
+
a bubbling `input` event before clicking Send. It reads replies from
|
|
32
|
+
`message-content .markdown` and retains the *peak* streamed text because Gemini
|
|
33
|
+
can re-mount the response node mid-stream. Responses must settle for six seconds
|
|
34
|
+
by default; JSON and XML must also parse before Gemx returns them.
|
|
36
35
|
|
|
37
36
|
## Install
|
|
38
37
|
|
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
`gemini.google.com` as if it were an API, using Playwright to enter a prompt,
|
|
5
5
|
submit it, and capture the structured reply.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*peak* streamed text because Gemini re-mounts the response node mid-stream.
|
|
7
|
+
Gemx enters prompts using the input routine verified by the browser-console
|
|
8
|
+
recovery script: it replaces the editor contents with a paragraph and dispatches
|
|
9
|
+
a bubbling `input` event before clicking Send. It reads replies from
|
|
10
|
+
`message-content .markdown` and retains the *peak* streamed text because Gemini
|
|
11
|
+
can re-mount the response node mid-stream. Responses must settle for six seconds
|
|
12
|
+
by default; JSON and XML must also parse before Gemx returns them.
|
|
14
13
|
|
|
15
14
|
## Install
|
|
16
15
|
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
This is a formalized version of a hack that drives ``gemini.google.com/app`` as
|
|
4
4
|
if it were an API. The non-obvious parts (and why this exists):
|
|
5
5
|
|
|
6
|
-
* **Input**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
empty and the turn errors with "I encountered an error". ``execCommand`` fires
|
|
10
|
-
the trusted ``beforeinput``/``input`` pair Quill honors, exactly like a paste.
|
|
6
|
+
* **Input** follows the browser-console recovery script: replace the editor's
|
|
7
|
+
contents with a paragraph and dispatch a bubbling ``input`` event before
|
|
8
|
+
clicking Send.
|
|
11
9
|
* **The response** lives in ``message-content .markdown`` (not a bare
|
|
12
10
|
``.markdown``), and Gemini re-mounts / empties that node after streaming, so we
|
|
13
11
|
retain the *peak* text seen rather than reading the DOM once at the end.
|
|
@@ -109,13 +107,13 @@ _INSERT_TEXT_JS = """(text) => {
|
|
|
109
107
|
const editor = document.querySelector('.ql-editor[contenteditable="true"]');
|
|
110
108
|
if (!editor) return false;
|
|
111
109
|
editor.focus();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return
|
|
110
|
+
editor.classList.remove('ql-blank');
|
|
111
|
+
editor.textContent = '';
|
|
112
|
+
const p = document.createElement('p');
|
|
113
|
+
p.textContent = text;
|
|
114
|
+
editor.appendChild(p);
|
|
115
|
+
editor.dispatchEvent(new Event('input', { bubbles: true }));
|
|
116
|
+
return true;
|
|
119
117
|
}"""
|
|
120
118
|
|
|
121
119
|
_INSTALL_RESPONSE_OBSERVER_JS = """(selectors) => {
|
|
@@ -297,9 +295,8 @@ class GemxConfig: # pylint: disable=too-many-instance-attributes
|
|
|
297
295
|
scroll_input_into_view: bool = True
|
|
298
296
|
# The peak response must exceed this length and stay non-growing (or be
|
|
299
297
|
# emptied) for this many poll ticks before it is accepted as complete.
|
|
300
|
-
# Matches the original bot/gemini.py driver (stable_count >= 2).
|
|
301
298
|
min_response_chars: int = 50
|
|
302
|
-
stable_ticks: int =
|
|
299
|
+
stable_ticks: int = 3
|
|
303
300
|
# How often (seconds) to emit page diagnostics while waiting for the response
|
|
304
301
|
# node to appear.
|
|
305
302
|
diagnostics_interval_s: int = 10
|
|
@@ -40,13 +40,13 @@ def format_instruction(fmt: OutputFormat) -> str:
|
|
|
40
40
|
"""Return the prompt instruction that asks Gemini for ``fmt`` output."""
|
|
41
41
|
if fmt is OutputFormat.JSON:
|
|
42
42
|
return (
|
|
43
|
-
"Respond with
|
|
44
|
-
"
|
|
43
|
+
"Respond with exactly one ```json fenced code block holding a single "
|
|
44
|
+
"valid JSON object, and nothing outside the block."
|
|
45
45
|
)
|
|
46
46
|
if fmt is OutputFormat.XML:
|
|
47
47
|
return (
|
|
48
|
-
"Respond with
|
|
49
|
-
"
|
|
48
|
+
"Respond with exactly one ```xml fenced code block holding a single "
|
|
49
|
+
"well-formed XML document, and nothing outside the block."
|
|
50
50
|
)
|
|
51
51
|
return (
|
|
52
52
|
"Respond with plain text only. Do not include markdown fences, JSON, or "
|
|
@@ -140,8 +140,17 @@ def parse_output(text: str, fmt: OutputFormat) -> Any:
|
|
|
140
140
|
end = repaired.rfind("}") + 1
|
|
141
141
|
candidate = repaired[start:end] if start != -1 and end != 0 else repaired
|
|
142
142
|
try:
|
|
143
|
-
return json.loads(candidate)
|
|
143
|
+
return json.loads(candidate, strict=False)
|
|
144
144
|
except json.JSONDecodeError as exc:
|
|
145
|
+
restarts = list(
|
|
146
|
+
re.finditer(r"```json[ \t]*\r?\n", text, flags=re.IGNORECASE)
|
|
147
|
+
)
|
|
148
|
+
if restarts:
|
|
149
|
+
candidate = text[restarts[-1].end() :].strip().removesuffix("```").rstrip()
|
|
150
|
+
try:
|
|
151
|
+
return json.loads(_clean_json_text(candidate), strict=False)
|
|
152
|
+
except json.JSONDecodeError:
|
|
153
|
+
pass
|
|
145
154
|
raise ValueError(f"Could not parse JSON from response: {exc}") from exc
|
|
146
155
|
|
|
147
156
|
start = cleaned.find("<")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|