feat(google): support service-account auth for TTS + Imagen, fix false availability
Google's TTS and Imagen tools advertised service-account auth
(GOOGLE_APPLICATION_CREDENTIALS) but only ever authenticated with an API
key string, so users with a service-account JSON could not use either tool.
google_tts.get_status() also over-reported availability when the JSON was
set, then failed at execute() — a silent-availability bug.
Separately, both hand-rolled _load_dotenv parsers kept inline comments as
values, so after `cp .env.example .env` every keyed tool falsely reported
"available" with no real credentials.
Changes:
- Add tools/google_credentials.py: lazy google-auth Bearer-token helper.
- google_tts: authenticate via Cloud TTS Bearer token when only a service
account is configured; make get_status() honest.
- google_imagen: route service-account auth to Vertex AI
({location}-aiplatform.googleapis.com) with project/location resolution,
alongside the existing AI Studio API-key path.
- Fix both _load_dotenv parsers to strip inline comments (quote-aware).
- Add google-auth to requirements; document the new env vars in .env.example.
- .gitignore: never commit GCP service-account key files.
Verified locally with a real service account: TTS produced a valid MP3 and
Imagen produced a valid 1408x768 PNG via Vertex AI. Existing test suite
passes (2 unrelated pre-existing failures only).
Closes #131
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-7
@@ -30,6 +30,7 @@ def _load_dotenv() -> None:
|
||||
env_path = Path(__file__).resolve().parent.parent / ".env"
|
||||
if not env_path.is_file():
|
||||
return
|
||||
import re
|
||||
with open(env_path, encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
@@ -37,13 +38,19 @@ def _load_dotenv() -> None:
|
||||
continue
|
||||
key, _, value = line.partition("=")
|
||||
key = key.strip()
|
||||
value = value.strip().strip("'\"")
|
||||
# Strip inline comments: VAR=value # comment
|
||||
# But only if the # is preceded by whitespace (avoid stripping from values like colors)
|
||||
if " #" in value:
|
||||
value = value[:value.index(" #")].rstrip()
|
||||
elif "\t#" in value:
|
||||
value = value[:value.index("\t#")].rstrip()
|
||||
value = value.strip()
|
||||
# Quoted value: take the content inside the quotes verbatim.
|
||||
if value[:1] in ("'", '"'):
|
||||
quote = value[0]
|
||||
end = value.find(quote, 1)
|
||||
value = value[1:end] if end != -1 else value[1:]
|
||||
else:
|
||||
# Strip an inline comment ('#' at line start or after
|
||||
# whitespace) so "VAR= # note" yields "" not "# note".
|
||||
match = re.search(r"(^|\s)#", value)
|
||||
if match:
|
||||
value = value[: match.start()]
|
||||
value = value.strip()
|
||||
if key and key not in os.environ:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
Reference in New Issue
Block a user