SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
41.6 KB · 363 lines python
Raw Blame History
1#!/usr/bin/env python32"""Merge subagent parts into the mandated fragment files and patch live-verification results (2026-09-18 run).3Run: .venv/bin/python tmp/gemini-parts/merge_patch.py4"""5from __future__ import annotations67import json8import re9from pathlib import Path1011ROOT = Path(__file__).resolve().parents[2]12PARTS = ROOT / "tmp" / "gemini-parts"13FRAG = ROOT / "generated" / "fragments"14DATE = "2026-09-18"15RAW = "tmp-live/gemini-tools"161718def load(p: Path) -> dict:19    return json.loads(p.read_text())202122def dump(p: Path, d: dict) -> None:23    p.parent.mkdir(parents=True, exist_ok=True)24    p.write_text(json.dumps(d, indent=1, ensure_ascii=False) + "\n")252627def ver(result: str, status: int | None, note: str) -> dict:28    return {"method": "live_api", "verified_at": DATE, "result": result, "http_status": status, "request_note": note}293031def add_status(rec: dict, *tags: str) -> None:32    st = [s for s in rec.get("status", []) if s not in ("UNVERIFIED",)]33    for t in tags:34        if t not in st:35            st.append(t)36    rec["status"] = st373839# ----------------------------------------------------------------------------- endpoints40EP_PATCH: dict[tuple[str, str], tuple[str, str, int | None, str]] = {41    ("POST", "/v1beta/fileSearchStores"): ("LIVE_VERIFIED", "success", 200, f"g1: {{displayName}} → {{name, displayName, createTime, updateTime, embeddingModel: models/gemini-embedding-001}} ({RAW}/g1_store_create.json)"),42    ("GET", "/v1beta/fileSearchStores"): ("LIVE_VERIFIED", "success", 200, "g7b: ?pageSize=5 → {fileSearchStores:[…]}"),43    ("GET", "/v1beta/fileSearchStores/{name}"): ("LIVE_VERIFIED", "success", 200, f"g7: store with activeDocumentsCount '1', sizeBytes '200' ({RAW}/g7_store_get.json)"),44    ("DELETE", "/v1beta/fileSearchStores/{name}"): ("LIVE_VERIFIED", "success", 200, "g9: DELETE …?force=true → 200 {} (store still held one document)"),45    ("POST", "/upload/v1beta/fileSearchStores/{name}:uploadToFileSearchStore"): ("LIVE_VERIFIED", "success", 200, f"g2/g3: resumable protocol — start (X-Goog-Upload-Protocol: resumable, -Command: start, JSON metadata displayName+customMetadata) → 200 + X-Goog-Upload-URL; then POST upload URL with 'upload, finalize' + 200 bytes → Operation {{name: fileSearchStores/…/upload/operations/…, response}} ({RAW}/g3_upload_finalize.json)"),46    ("GET", "/v1beta/fileSearchStores/{name}/upload/operations/{operation}"): ("LIVE_VERIFIED", "success", 200, f"g4: polled until done:true; response {{@type, documentName, mimeType, parent, sizeBytes}} ({RAW}/g4_operation_done.json)"),47    ("GET", "/v1beta/fileSearchStores/{name}/documents"): ("LIVE_VERIFIED", "success", 200, f"g5: {{documents:[{{name, displayName, mimeType, sizeBytes, state: STATE_ACTIVE, customMetadata, createTime, updateTime}}]}} ({RAW}/g5_documents_list.json)"),48    ("GET", "/v1beta/fileSearchStores/{name}/documents/{document}"): ("LIVE_VERIFIED", "success", 200, "g5b: same Document shape as list"),49    ("DELETE", "/v1beta/fileSearchStores/{name}/documents/{document}"): ("LIVE_VERIFIED", "success", 200, "g8 without force → 400 FAILED_PRECONDITION 'Cannot delete non-empty Document'; with ?force=true → 200 (examples/file_search_lifecycle.py, test_file_search.py)"),50    ("POST", "/v1beta/interactions"): ("LIVE_VERIFIED", "success", 200, f"h1 create (steps [thought, model_output], usage), h2 stream:true (SSE event:/data:, 9 events), h3 previous_interaction_id, h5 store:false (no id), h6 function tool → requires_action, h8 agent deep-research-preview-04-2026 background:true → in_progress; f2 tools[{{type: computer_use, environment: browser}}] accepted on gemini-3.5-flash-lite ({RAW}/h*.json). Burst of ~10 calls/min → HTTP 429 too_many_requests observed once in pytest."),51    ("GET", "/v1beta/interactions/{id}"): ("LIVE_VERIFIED", "success", 200, "h4: full Interaction incl. generation_config echo; after DELETE → 404 'Requested entity was not found'; ?include_steps=true → 400; unstored (store:false) interactions have no id"),52    ("DELETE", "/v1beta/interactions/{id}"): ("LIVE_VERIFIED", "success", 200, "h9: → 200 {}"),53    ("POST", "/v1beta/interactions/{id}/cancel"): ("LIVE_VERIFIED", "success", 200, f"h8b: cancel of an in_progress deep-research background interaction → 200 status 'cancelled'; GET afterwards 'cancelled' ({RAW}/h8b_deep_research_cancel.json)"),54    ("POST", "/v1beta/environments"): ("LIVE_VERIFIED", "success", 200, f"env2: {{}} → 200 {{id}} ({RAW}/env2_environment_create.json)"),55    ("GET", "/v1beta/environments"): ("LIVE_VERIFIED", "success", 200, "env1: → 200 {} (no environments); discovery spelling GET /v1beta/environments:list → 200 too (env6)"),56    ("GET", "/v1beta/environments/{id}"): ("LIVE_VERIFIED", "success", 200, "env3: {id, status: active, storage: {tier: free, project_used_bytes, project_limit_bytes: 1073741824}, file_count, size_bytes, created, updated}"),57    ("DELETE", "/v1beta/environments/{id}"): ("LIVE_VERIFIED", "success", 200, "env5: → 200 {}"),58    ("GET", "/v1beta/environments/{environment}/files/{path}"): ("LIVE_VERIFIED", "partial", 200, "env4: GET …/files/ on a fresh empty environment → 404 in the first run, 200 {} in the second (examples/gemini/interactions/environments.py); file download not exercised"),59    ("POST", "/v1beta/auth_tokens"): ("LIVE_VERIFIED", "success", 200, f"i: {{uses:1, expireTime, newSessionExpireTime}} → 200 {{name: auth_tokens/…}} on BOTH /v1alpha and /v1beta; with bidiGenerateContentSetup{{model, generationConfig}} lock → 200 (j9c). Token redacted in {RAW}/i_auth_token_*.json"),60    ("WSS", "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent"): ("LIVE_VERIFIED", "success", 101, f"j1/j4/j5: x-goog-api-key header on the handshake; gemini-2.5-flash-native-audio-latest, gemini-3.1-flash-live-preview and gemini-3.8-live ALL close 1007 on responseModalities [TEXT] ('not supported by the model'); AUDIO turn → setupComplete → serverContent… → turnComplete+usageMetadata; setup.toolConfig → close 1007 'Unknown name toolConfig'; toolCall received for a NON_BLOCKING declaration; ephemeral token on this method → close 1008 ({RAW}/j*.json)"),61    ("WSS", "/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContentConstrained"): ("LIVE_VERIFIED", "success", 101, "j9a/j9b/j9c on the v1alpha spelling: Authorization: Token <auth_tokens/…> header AND ?access_token= query both reached setupComplete → turnComplete; a setup-locked token also worked (docs: v1alpha for ephemeral tokens; SDK uses v1alpha + this method)"),62    ("WSS", "/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent"): ("LIVE_VERIFIED", "failure", None, "j8a/j8b: ephemeral token on the plain v1alpha BidiGenerateContent method → close 1008 'Method doesn't allow unregistered callers'; use …BidiGenerateContentConstrained instead"),63}64NOT_TESTED_NOTE = "not exercised in the 2026-09-18 run (docs only)"656667def norm(p: str) -> str:68    return re.sub(r"\{[^}]+\}", "{}", p)697071def merge_endpoints() -> None:72    recs: list[dict] = []73    for part in ("tools-endpoints", "interactions-endpoints", "live-endpoints"):74        recs += load(PARTS / f"{part}.json")["records"]75    seen = set()76    for r in recs:77        key = (r["method"], norm(r["path"]))78        hit = next((v for (m, p), v in EP_PATCH.items() if m == r["method"] and norm(p) == norm(r["path"])), None)79        if hit:80            tag, result, http, note = hit81            r["verification"] = ver(result, http, note)82            add_status(r, tag)83            if r["path"].startswith("/v1beta/interactions") or r["path"].startswith("/v1beta/environments"):84                add_status(r, "LIVE_VERIFIED") if result != "failure" else None85            seen.add(key)86        else:87            if r.get("verification", {}).get("result") in (None, "not_tested"):88                r["verification"] = {"method": "docs_only", "verified_at": DATE, "result": "not_tested", "http_status": None, "request_note": NOT_TESTED_NOTE}89    missing = [k for k in EP_PATCH if (k[0], norm(k[1])) not in {(r["method"], norm(r["path"])) for r in recs}]90    if missing:91        print("EP_PATCH keys without a record:", missing)92    # a live-discovered list probe (documented nowhere): record as LIVE_DISCOVERED failure93    recs.append({"provider": "gemini", "api_family": "interactions", "method": "GET", "path": "/v1beta/interactions", "name": "List interactions (probe)",94                 "description": "Not documented. Probed 2026-09-18: HTTP 404 (empty error body). Interactions are retrieved individually by id; there is no documented list endpoint.",95                 "status": ["UNVERIFIED", "FAILED_VERIFICATION"], "auth": "x-goog-api-key header", "beta_header": None,96                 "request": {"content_type": None, "body_ref": None, "path_params": [], "query_params": [], "header_params": []},97                 "response": {"success_status": None, "content_type": None, "body_ref": None, "error_statuses": [404]},98                 "streaming": {"supported": False, "events_ref": None, "note": None}, "pagination": None, "idempotency": None, "sdk": {"python": None, "node": None}, "relations": [],99                 "verification": ver("failure", 404, "h0: GET /v1beta/interactions → 404 with empty body"),100                 "sources": [{"url": "https://ai.google.dev/api/interactions", "retrieved_at": DATE, "note": "no list method documented"}]})101    dump(FRAG / "endpoints" / "gemini-tools-agents-live.json", {102        "$fragment": "endpoints/gemini-tools-agents-live", "generated_by": "agent: gemini tools/agents/live (3 subagents + parent live run, merged by tmp/gemini-parts/merge_patch.py)",103        "generated_at": DATE, "notes": "File Search stores/documents/operations, Interactions API (+ credentials, agents, triggers, webhooks), Environments API, dynamic/*, auth_tokens, Live WebSocket pseudo-endpoints. Live verification 2026-09-18 with gemini-3.5-flash-lite / gemini-2.5-flash-native-audio-latest; raws in tmp-live/gemini-tools/.",104        "records": recs})105    print("endpoints:", len(recs), "patched:", len(seen))106107108# ----------------------------------------------------------------------------- objects109def merge_objects() -> None:110    recs: list[dict] = []111    for part in ("tools-objects", "interactions-objects", "live-objects"):112        recs += load(PARTS / f"{part}.json")["records"]113    live_seen = {114        "GroundingMetadata": "maps run: {webSearchQueries, groundingChunks[{maps}], groundingSupports}; fileSearch run: groundingChunks[{retrievedContext{title,text,fileSearchStore(,customMetadata)}}] + groundingSupports (absent once)",115        "GroundingChunk": "live shapes: {maps:{uri,title,text,placeId}}, {retrievedContext:{title,text,fileSearchStore}}",116        "UrlContextMetadata": "live: {urlMetadata:[{retrievedUrl, urlRetrievalStatus: URL_RETRIEVAL_STATUS_ERROR}]}",117        "FunctionCall": "live: {name, args, id: call_…} + sibling part field thoughtSignature (first call of a parallel turn only); Live API toolCall ids look like function-call-<digits>",118        "ExecutableCode": "live: {language: PYTHON, code, id: call_…}",119        "CodeExecutionResult": "live: {outcome: OUTCOME_OK, output: '4\\n', id: call_…}",120        "FileSearchStore": "live: {name, displayName, createTime, updateTime, embeddingModel: models/gemini-embedding-001, activeDocumentsCount, sizeBytes}",121        "Document": "live: {name, displayName, mimeType, sizeBytes, state: STATE_ACTIVE, customMetadata, createTime, updateTime}",122        "Operation": "live upload operation: {name: …/upload/operations/…, response:{@type, documentName, mimeType, parent, sizeBytes}, done:true}",123        "Interaction": "live: {id, object: interaction, status, model|agent, created, updated, service_tier, steps[], usage{total_tokens,total_input_tokens,input_tokens_by_modality,total_cached_tokens,total_output_tokens,total_tool_use_tokens,total_thought_tokens,raw_prompt_token,model_invocation_token_counts}, generation_config (GET echo)}; store:false → no id",124        "Environment": "live: {id, status: active, storage{tier, project_used_bytes, project_limit_bytes}, file_count, size_bytes, created, updated}",125        "AuthToken": "live: response is exactly {name: 'auth_tokens/…'}",126        "BidiGenerateContentSetupComplete": "live: {} (empty object)",127        "BidiGenerateContentServerContent": "live fields seen: modelTurn (parts text+thought:true, then inlineData audio/pcm;rate=24000), outputTranscription{text}, generationComplete, turnComplete",128        "BidiGenerateContentToolCall": "live: {functionCalls:[{name, args, id: function-call-…}]}",129        "SessionResumptionUpdate": "live: {newHandle: <uuid>, resumable: true} right after setupComplete and again after turnComplete",130        "UsageMetadata": "live (Live API): {promptTokenCount, responseTokenCount, totalTokenCount, promptTokensDetails[{modality:TEXT}], responseTokensDetails[{modality:AUDIO}], thoughtsTokenCount} attached to the turnComplete message",131    }132    n = 0133    for r in recs:134        key = r.get("schema_name") or r.get("object")135        for k, note in live_seen.items():136            if key == k or r.get("object") == k or r.get("name") == k:137                r["live_observation_2026_09_18"] = note138                add_status(r, "LIVE_VERIFIED")139                n += 1140                break141    dump(FRAG / "objects" / "gemini-tools-agents-objects.json", {142        "$fragment": "objects/gemini-tools-agents-objects", "generated_by": "agent: gemini tools/agents/live (3 subagents, merged by tmp/gemini-parts/merge_patch.py)",143        "generated_at": DATE, "notes": "Tool/grounding objects, File Search objects, Interactions API objects, Environments, Live API messages, AuthToken, computer-use action catalogue. Records carrying live_observation_2026_09_18 were compared with real responses.",144        "records": recs})145    print("objects:", len(recs), "annotated:", n)146147148# ----------------------------------------------------------------------------- tools149TOOL_PATCH = {150    "functionDeclarations": ("LIVE_VERIFIED", "success", 200, f"a1 mode ANY+allowedFunctionNames → functionCall{{name,args,id:call_…}}+thoughtSignature; a2 functionResponse → text; a2b WITHOUT thoughtSignature → 400 'Function call is missing a thought_signature'; a3 parallel: 2 calls, signature on the first only; a4 parametersJsonSchema OK; a5 NONE OK; a6 mode VALIDATED accepted (200, functionCall); a7 behavior NON_BLOCKING → 400 'only supported by the BidiGenerateContent method'; Live j5: toolCall{{functionCalls[{{name,args,id:function-call-…}}]}} ({RAW}/a*.json, j5_ws_toolcall.json)"),151    "googleSearch": ("ACCOUNT_RESTRICTED", "restricted", 429, f"b/b2: tools:[{{googleSearch:{{}}}}] on gemini-3.5-flash-lite AND gemini-3.5-flash → 429 RESOURCE_EXHAUSTED 'You exceeded your current quota' (no per-metric detail); Interactions {{type: google_search}} → 429 too_many_requests. Consistent with the pricing page 'Not available' on the free tier; the project appears to be on the free tier (computer-use quota message: generate_content_free_tier_input_token_count limit 0) ({RAW}/b_google_search.json)"),152    "googleMaps": ("LIVE_VERIFIED", "success", 200, f"e: tools:[{{googleMaps:{{}}}}] + toolConfig.retrievalConfig.latLng (Toronto) → groundingMetadata {{webSearchQueries, groundingChunks[{{maps:{{uri,title,text,placeId}}}}], groundingSupports}}; e2 enableWidget:true → 200 but no googleMapsWidgetContextToken on gemini-3.5-flash-lite ({RAW}/e_google_maps.json)"),153    "urlContext": ("LIVE_VERIFIED", "success", 200, f"c: candidate.urlContextMetadata.urlMetadata[{{retrievedUrl, urlRetrievalStatus: URL_RETRIEVAL_STATUS_ERROR}}] for https://example.com; usageMetadata.toolUsePromptTokenCount 130 ({RAW}/c_url_context.json)"),154    "codeExecution": ("LIVE_VERIFIED", "success", 200, f"d: parts executableCode{{language:PYTHON,code,id}} → codeExecutionResult{{outcome:OUTCOME_OK,output:'4\\n',id}} → text; toolUsePromptTokenCount 34 ({RAW}/d_code_execution.json)"),155    "computerUse": ("ACCOUNT_RESTRICTED", "restricted", 429, f"f: generateContent computerUse{{environment:ENVIRONMENT_BROWSER}} on gemini-2.5-computer-use-preview-10-2025 → 429 'Quota exceeded for metric generate_content_free_tier_input_token_count, limit: 0, model: computer-use-preview'; f2: Interactions tools:[{{type:computer_use, environment:browser}}] on gemini-3.5-flash-lite → 200 completed (model replied DONE without acting) ({RAW}/f*_computer_use*.json)"),156    "fileSearch": ("LIVE_VERIFIED", "success", 200, f"g6: tools:[{{fileSearch:{{fileSearchStoreNames:[…], metadataFilter:'kind=probe'}}}}] → correct answer from a 200-byte txt; groundingMetadata.groundingChunks[{{retrievedContext{{title,text,fileSearchStore}}}}] + groundingSupports present in 2 of 3 runs, absent once; toolUsePromptTokenCount 834–985 ({RAW}/g6_file_search_query.json)"),157}158159160def patch_tools() -> None:161    p = FRAG / "tools" / "gemini-tools.json"162    d = load(p)163    for r in d["records"]:164        hit = TOOL_PATCH.get(r["type"])165        if hit:166            tag, result, http, note = hit167            r["verification"] = ver(result, http, note)168            add_status(r, tag)169        elif r.get("verification", {}).get("result") in (None, "not_tested"):170            r["verification"]["request_note"] = NOT_TESTED_NOTE171    d["live_run_2026_09_18"] = {"models": ["gemini-3.5-flash-lite", "gemini-3.5-flash (search retry)", "gemini-2.5-computer-use-preview-10-2025", "gemini-2.5-flash-native-audio-latest"],172                                "raw": "tmp-live/gemini-tools/", "summary": "tmp-live/gemini-tools/_summary.json"}173    dump(p, d)174    print("tools patched")175176177# ----------------------------------------------------------------------------- streaming events178LIVE_SEQ = ["setupComplete", "sessionResumptionUpdate", "serverContent.modelTurn(text,thought:true)", "serverContent.outputTranscription", "serverContent.modelTurn(inlineData audio/pcm;rate=24000) ×N",179            "serverContent.generationComplete", "serverContent.turnComplete + usageMetadata (same message)", "sessionResumptionUpdate"]180LIVE_SEEN = {"setup": "sent; accepted (setupComplete followed)", "clientContent": "sent with turns[] + turnComplete:true", "setupComplete": "received as {} immediately after setup",181             "serverContent": "received", "serverContent.modelTurn": "parts: first a {text, thought:true} summary, then {inlineData:{mimeType:'audio/pcm;rate=24000', data}} chunks (0–6 chunks for 'OK')",182             "serverContent.outputTranscription": "{text:'OK'} (needs setup.outputAudioTranscription:{})", "serverContent.generationComplete": "true, one message before turnComplete",183             "serverContent.turnComplete": "true; usageMetadata rides in the same message", "usageMetadata": "{promptTokenCount, responseTokenCount, totalTokenCount, promptTokensDetails, responseTokensDetails[{modality:AUDIO}], thoughtsTokenCount}",184             "toolCall": "{functionCalls:[{name,args,id:'function-call-…'}]} after the thought part, for a NON_BLOCKING declaration (mode ANY not settable: setup.toolConfig is rejected)",185             "sessionResumptionUpdate": "{newHandle, resumable:true} right after setupComplete and after turnComplete when setup.sessionResumption:{} is set"}186INT_SEEN = {"interaction.created": "{interaction:{id,status:in_progress,object,model}, event_type}", "interaction.status_update": "{interaction_id, status:in_progress, event_type}",187            "step.start": "{index, step:{type}, event_type} — types seen: thought, model_output", "step.delta": "{index, delta:{type:'thought_signature', signature} | {type:'text', text}, event_type}",188            "step.stop": "{index, event_type}", "interaction.completed": "{interaction:{full Interaction incl. usage}, event_type}", "done": "terminal SSE frame 'event: done' / 'data: [DONE]'"}189190191def patch_events() -> None:192    p = FRAG / "streaming-events" / "gemini-live.json"193    d = load(p)194    for r in d["records"]:195        if r["event"] in LIVE_SEEN:196            r["live_observation_2026_09_18"] = LIVE_SEEN[r["event"]]197            add_status(r, "LIVE_VERIFIED")198    d["observed_server_sequence_audio_turn_2026_09_18"] = LIVE_SEQ199    d["observed_rejections_2026_09_18"] = ["responseModalities [TEXT] → close 1007 on gemini-2.5-flash-native-audio-latest, gemini-3.1-flash-live-preview, gemini-3.8-live",200                                          "setup.toolConfig → close 1007 'Unknown name toolConfig at setup'", "ephemeral token on BidiGenerateContent → close 1008 (use BidiGenerateContentConstrained)"]201    dump(p, d)202    p = FRAG / "streaming-events" / "gemini-interactions.json"203    d = load(p)204    for r in d["records"]:205        if r["event"] in INT_SEEN and "delta_type" not in r or (r["event"] == "step.delta" and r.get("delta_type") in ("text", "thought_signature")):206            r["live_observation_2026_09_18"] = INT_SEEN[r["event"]]207            add_status(r, "LIVE_VERIFIED")208    d["observed_sequence_2026_09_18"] = ["interaction.created", "interaction.status_update", "step.start(thought)", "step.delta(thought_signature)", "step.stop", "step.start(model_output)", "step.delta(text)", "step.stop", "interaction.completed", "done"]209    dump(p, d)210    print("events patched")211212213# ----------------------------------------------------------------------------- matrix214MATRIX = {("functionDeclarations", "gemini-3.5-flash-lite"): (True, "LIVE_VERIFIED", "a1–a6 OK"), ("googleMaps", "gemini-3.5-flash-lite"): (True, "LIVE_VERIFIED", "e OK"),215          ("urlContext", "gemini-3.5-flash-lite"): (True, "LIVE_VERIFIED", "c OK"), ("codeExecution", "gemini-3.5-flash-lite"): (True, "LIVE_VERIFIED", "d OK"),216          ("fileSearch", "gemini-3.5-flash-lite"): (True, "LIVE_VERIFIED", "g6 OK"), ("googleSearch", "gemini-3.5-flash-lite"): (None, "ACCOUNT_RESTRICTED", "429 RESOURCE_EXHAUSTED (quota) — documented as supported"),217          ("googleSearch", "gemini-3.5-flash"): (None, "ACCOUNT_RESTRICTED", "429 RESOURCE_EXHAUSTED (quota)"), ("computerUse", "gemini-2.5-computer-use-preview-10-2025"): (None, "ACCOUNT_RESTRICTED", "429 free-tier quota 0"),218          ("functionDeclarations", "gemini-2.5-flash-native-audio-preview-12-2025"): (True, "LIVE_VERIFIED", "Live toolCall on gemini-2.5-flash-native-audio-latest (alias) with behavior NON_BLOCKING")}219220221def patch_matrix() -> None:222    p = FRAG / "compatibility" / "gemini-tool-model-matrix.json"223    d = load(p)224    hit = 0225    for r in d["records"]:226        k = (r["tool_type"], r["model"])227        if k in MATRIX:228            sup, tag, note = MATRIX[k]229            if sup is not None:230                r["supported"] = sup231            add_status(r, tag)232            r["notes"] = (r.get("notes") or "") + f" | live 2026-09-18: {note}"233            hit += 1234    d["legend"]["live_run"] = "LIVE_VERIFIED/ACCOUNT_RESTRICTED rows were probed 2026-09-18 (tmp-live/gemini-tools/); Interactions API computer_use accepted on gemini-3.5-flash-lite (f2)"235    dump(p, d)236    print("matrix patched:", hit)237238239# ----------------------------------------------------------------------------- docs240PLACEHOLDER = "_Filled in by the parent agent after the live run._"241DOCS = {242    "docs/tools/gemini/index.md": """Run of 2026-09-18 on `gemini-3.5-flash-lite` (≈ 60 REST calls + 12 WebSocket sessions, estimated ≈ $0.10; raw sanitized responses in `tmp-live/gemini-tools/`, summary `_summary.json`, log notes `gemini-tools:*` in `reports/live-requests.jsonl`).243244| Tool | Result | Key observation |245|---|---|---|246| `functionDeclarations` | **LIVE_VERIFIED** | `functionCall{name,args,id:"call_…"}` + `thoughtSignature`; echoing the model turn without the signature → **400** `Function call is missing a thought_signature`; parallel turn: 2 calls, signature only on the first; `parametersJsonSchema` OK; modes `ANY`/`NONE`/`VALIDATED` accepted; `behavior: NON_BLOCKING` → 400 outside Live |247| `googleSearch` | **ACCOUNT_RESTRICTED** | 429 `RESOURCE_EXHAUSTED` on gemini-3.5-flash-lite and gemini-3.5-flash (free-tier project; also via Interactions `google_search`) |248| `googleMaps` | **LIVE_VERIFIED** | `groundingChunks[].maps{uri,title,text,placeId}`, `webSearchQueries`, `groundingSupports`; `enableWidget` returned no `googleMapsWidgetContextToken` |249| `urlContext` | **LIVE_VERIFIED** | `candidate.urlContextMetadata.urlMetadata[{retrievedUrl, urlRetrievalStatus}]` (example.com → `URL_RETRIEVAL_STATUS_ERROR`), `usageMetadata.toolUsePromptTokenCount` |250| `codeExecution` | **LIVE_VERIFIED** | parts `executableCode{language,code,id}` → `codeExecutionResult{outcome,output,id}` → `text` |251| `computerUse` (generateContent, 2.5 model) | **ACCOUNT_RESTRICTED** | 429 `generate_content_free_tier_input_token_count, limit: 0`; Interactions `{"type":"computer_use","environment":"browser"}` accepted (200) on gemini-3.5-flash-lite |252| `fileSearch` | **LIVE_VERIFIED** | store → resumable upload → operation → `STATE_ACTIVE` → grounded answer; `groundingChunks[].retrievedContext{title,text,fileSearchStore}` (absent in 1 of 3 runs); document delete needs `?force=true` |253| `googleSearchRetrieval`, `mcpServers`, SDK MCP | not tested | no live 2.0 target / undocumented / SDK-side |254255Machine-readable twins updated: `generated/fragments/tools/gemini-tools.json` (verification blocks), `compatibility/gemini-tool-model-matrix.json` (LIVE rows), `endpoints/gemini-tools-agents-live.json`, `objects/gemini-tools-agents-objects.json`. Examples: `examples/gemini/tools/**`, `examples/shared/tool-loop/gemini_tool_loop.{py,ts}`; tests `tests/gemini/test_tools.py`.""",256    "docs/tools/gemini/function-calling.md": """`gemini-3.5-flash-lite`, raws `tmp-live/gemini-tools/a*.json` (all HTTP 200 unless stated):257258- **Forced call** (`toolConfig.functionCallingConfig.mode: ANY` + `allowedFunctionNames`) → one part `{functionCall:{name:"get_weather", args:{city:"Paris"}, id:"call_356059"}, thoughtSignature:"…"}`, `finishReason: STOP`.259- **Round trip**: appending the model turn verbatim + `{functionResponse:{name, response:{…}}}` → text answer. Stripping `thoughtSignature` → **400** `Function call is missing a thought_signature in functionCall parts. This is required for tools to work correctly… function call default_api:get_weather, position 2`.260- **Parallel**: two declarations, mode ANY → parts `[functionCall+thoughtSignature, functionCall]` (`call_243490`, `call_243491`) — only the first carries a signature, exactly as the docs describe.261- **`parametersJsonSchema`** (plain JSON Schema with an enum) → `args {city:"Rome", unit:"C"}`.262- **`mode: NONE`** → text only; **`mode: VALIDATED`** → accepted (functionCall returned).263- **`behavior: NON_BLOCKING`** in generateContent → **400** `FunctionDeclaration.behavior is only supported by the BidiGenerateContent method`. In the Live API the same declaration produced `toolCall{functionCalls:[{name,args,id:"function-call-11967657623789764093"}]}` and the model kept talking (audio) — non-blocking behaviour confirmed; `setup.toolConfig` is **rejected** (close 1007), so the mode cannot be forced in Live.264- Interactions API twin: `tools:[{type:"function",…}]` + `generation_config.tool_choice{allowed_tools{mode:"any", tools:[…]}}` → `status:"requires_action"`, step `{id:"call_297992", type:"function_call", name, arguments}`; top-level `tool_choice` → 400 `Unknown parameter 'tool_choice'`; reply with `input:[{type:"function_result", name, call_id, result:[{type:"text", text}]}]` + `previous_interaction_id`.265266Examples: `examples/gemini/tools/function-calling/{function_calling.py,forced_call.sh,function_calling.ts}`, `examples/shared/tool-loop/gemini_tool_loop.{py,ts}` (all executed). Tests: `tests/gemini/test_tools.py`.""",267    "docs/tools/gemini/google-search-grounding.md": """**ACCOUNT_RESTRICTED.** `tools:[{"googleSearch":{}}]` with `"What is today's date in Toronto? 5 words."` returned **HTTP 429** `RESOURCE_EXHAUSTED` — `You exceeded your current quota, please check your plan and billing details` — on `gemini-3.5-flash-lite` and again on `gemini-3.5-flash` (`tmp-live/gemini-tools/b_google_search.json`, `b2_google_search_flash.json`); the Interactions API `{"type":"google_search"}` gave the same 429 (`h7`). The computer-use probe on the same key named the metric `generate_content_free_tier_input_token_count`, so the project is on the free tier, where the pricing page lists Search grounding as "Not available". The `groundingMetadata` anatomy above is therefore documented-only for this run (the Maps run confirmed the shared `groundingChunks`/`groundingSupports`/`webSearchQueries` container). Example `examples/gemini/tools/google-search/grounding.py` prints the 429 verbatim; `tests/gemini/test_tools.py::test_google_search_grounding` is gated by `RUN_EXPENSIVE_TESTS` and skips on 429.""",268    "docs/tools/gemini/google-maps-grounding.md": """**LIVE_VERIFIED** on `gemini-3.5-flash-lite` (`tmp-live/gemini-tools/e_google_maps.json`): `tools:[{"googleMaps":{}}]` + `toolConfig.retrievalConfig.latLng{latitude:43.6532, longitude:-79.3832}` → HTTP 200, `groundingMetadata` keys `webSearchQueries`, `groundingChunks`, `groundingSupports`; each chunk is `{maps:{uri:"https://maps.google.com/maps?cid=…", title, text (address/rating/hours markdown), placeId:"ChIJ…"}}`. With `googleMaps.enableWidget:true` (`e2_google_maps_widget.json`) the response had **no** `googleMapsWidgetContextToken` on this model. No 429 — Maps grounding worked on the same key that is blocked for Search grounding. Example `examples/gemini/tools/google-maps/maps_grounding.py`, test `tests/gemini/test_tools.py::test_google_maps_grounding`.""",269    "docs/tools/gemini/url-context.md": """**LIVE_VERIFIED** on `gemini-3.5-flash-lite` (`tmp-live/gemini-tools/c_url_context.json`): `tools:[{"urlContext":{}}]` + `"Summarize https://example.com in 5 words."` → HTTP 200; `candidates[0].urlContextMetadata = {urlMetadata:[{retrievedUrl:"https://example.com", urlRetrievalStatus:"URL_RETRIEVAL_STATUS_ERROR"}]}` (example.com could not be fetched, the model still answered from memory); `usageMetadata` gained `toolUsePromptTokenCount: 130` + `toolUsePromptTokensDetails` — the retrieved content is billed as input tokens even when retrieval fails. Example `examples/gemini/tools/url-context/url_context.py`, test `tests/gemini/test_tools.py::test_url_context_metadata`.""",270    "docs/tools/gemini/code-execution.md": """**LIVE_VERIFIED** on `gemini-3.5-flash-lite` (`tmp-live/gemini-tools/d_code_execution.json`): `tools:[{"codeExecution":{}}]` + `"Run print(2+2) in Python and report the output."` → HTTP 200 with parts `[{executableCode:{language:"PYTHON", code:"print(2+2)", id:"call_348726"}, thoughtSignature}, {codeExecutionResult:{outcome:"OUTCOME_OK", output:"4\\n", id:"call_348726"}}, {text:"4", thoughtSignature}]`. Note the `id` field on both parts (same value) — not shown in the guide. `usageMetadata.toolUsePromptTokenCount: 34`. Examples `examples/gemini/tools/code-execution/code_execution.{py,sh}` (both executed), test `tests/gemini/test_tools.py::test_code_execution_parts`.""",271    "docs/tools/gemini/computer-use.md": """- Legacy generateContent tool `tools:[{"computerUse":{"environment":"ENVIRONMENT_BROWSER"}}]` on `gemini-2.5-computer-use-preview-10-2025` with a 1×1 PNG → **HTTP 429** `Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: computer-use-preview` → **ACCOUNT_RESTRICTED** (`tmp-live/gemini-tools/f_computer_use_generateContent.json`).272- Interactions API `tools:[{"type":"computer_use","environment":"browser"}]` on `gemini-3.5-flash-lite`, input "Do nothing. Reply DONE." → **HTTP 200**, `status:"completed"`, steps `[thought, model_output "DONE"]` — the tool declaration is accepted on a non-computer-use model; no action was requested so the `function_call` shape was not observed live (`f2_computer_use_interactions.json`).273- The client loop (Playwright, `function_result` with screenshot + url, `safety_decision` acknowledgement) in `examples/gemini/tools/computer-use/computer_use_interactions.py --loop` is DOCUMENTED, not executed.""",274    "docs/tools/gemini/file-search.md": """**LIVE_VERIFIED** end-to-end (`tmp-live/gemini-tools/g*.json`, `examples/gemini/file-search/file_search_lifecycle.py`, `file_search.ts`, `tests/gemini/test_file_search.py`):275276| Step | Call | Observed |277|---|---|---|278| create | `POST /v1beta/fileSearchStores {displayName}` | 200 `{name:"fileSearchStores/atlasprobe20260918-…", displayName, createTime, updateTime, embeddingModel:"models/gemini-embedding-001"}` |279| upload (resumable) | `POST /upload/v1beta/{store}:uploadToFileSearchStore` headers `X-Goog-Upload-Protocol: resumable`, `-Command: start`, JSON `{displayName, customMetadata, chunkingConfig}` | 200 + `X-Goog-Upload-URL` (+ `X-Goog-Upload-Control-URL`, `-Chunk-Granularity`, `-Status`) |280| finalize | `POST <upload url>` `X-Goog-Upload-Command: upload, finalize`, 200 bytes | 200 `Operation{name:".../upload/operations/…", response}` (`done` omitted) |281| poll | `GET /v1beta/{operation}` | `done:true`, `response{@type, documentName, mimeType, parent, sizeBytes}` |282| documents | `GET …/documents`, `GET …/documents/{doc}` | `{name, displayName, mimeType, sizeBytes, state:"STATE_ACTIVE", customMetadata, createTime, updateTime}` |283| query | `tools:[{fileSearch:{fileSearchStoreNames:[…], metadataFilter:"kind=probe"}}]` | correct answer; `groundingMetadata.groundingChunks[{retrievedContext{title, text, fileSearchStore}}]` + `groundingSupports` in 2 of 3 runs, **absent once** (only `usageMetadata.toolUsePromptTokenCount` 834–985 proved retrieval) |284| store get / list | `GET /v1beta/{store}`, `GET /v1beta/fileSearchStores?pageSize=5` | `activeDocumentsCount:"1"`, `sizeBytes:"200"`; `{fileSearchStores:[…]}` |285| delete document | `DELETE /v1beta/{doc}` | **400** `FAILED_PRECONDITION "Cannot delete non-empty Document"`; with `?force=true` → 200 |286| delete store | `DELETE /v1beta/{store}?force=true` | 200 `{}` |287288Not exercised: `importFile`, `/operations/{op}` poller for imports, `media/{blob}` download.""",289    "docs/tools/gemini/mcp.md": """Not exercised live (SDK-side feature; `tools[].mcpServers` server-side variant is undocumented outside the REST reference). No MCP server was available in the sandbox; the automatic-function-calling loop that the SDK MCP integration relies on is the same one verified in `examples/shared/tool-loop/gemini_tool_loop.py`.""",290    "docs/gemini/interactions-api.md": """Run of 2026-09-18 on `gemini-3.5-flash-lite` (raws `tmp-live/gemini-tools/h*.json`, `env*.json`, `f2_*.json`; examples `examples/gemini/interactions/*` all executed; tests `tests/gemini/test_interactions.py` pass).291292| Probe | Result |293|---|---|294| `POST /v1beta/interactions {model, input:"Reply with OK.", generation_config:{max_output_tokens:16}}` | 200 `{id:"v1_Chd…", object:"interaction", status:"completed", model, created, updated, service_tier:"standard", steps:[{type:"thought", signature}, {type:"model_output", content:[{type:"text", text:"OK."}]}], usage:{total_tokens, total_input_tokens, input_tokens_by_modality, total_cached_tokens, total_output_tokens, total_tool_use_tokens, total_thought_tokens, raw_prompt_token, model_invocation_token_counts}}` — note **`steps`**, not `outputs` |295| `stream:true` (+ `previous_interaction_id`) | SSE `event:`/`data:` frames, order **interaction.created → interaction.status_update → step.start(thought) → step.delta{type:"thought_signature"} → step.stop → step.start(model_output) → step.delta{type:"text"} → step.stop → interaction.completed** (+ terminal `data: [DONE]`) |296| `previous_interaction_id` chaining | 200; the model recalled the earlier turn ("You asked me to reply."); the response does **not** echo `previous_interaction_id` |297| `GET /v1beta/interactions/{id}` | 200 (adds `generation_config` echo); `?include_steps=true` → 400; after DELETE → 404 `Requested entity was not found` |298| `DELETE /v1beta/interactions/{id}` | 200 `{}` |299| `GET /v1beta/interactions` (list, undocumented) | 404 empty body |300| `store:false` | 200, **no `id`** in the response |301| function tool + `generation_config.tool_choice{allowed_tools{mode:"any"}}` | `status:"requires_action"`, step `{id:"call_…", type:"function_call", name, arguments}`; `function_result{name, call_id, result:[{type:"text",text}]}` + `previous_interaction_id` → completed. Top-level `tool_choice` → 400 `Unknown parameter 'tool_choice'`; camelCase (`generationConfig`) → 400 `Did you mean 'generation_config'?` |302| `{"type":"google_search"}` | 429 `too_many_requests` (same free-tier quota block as generateContent `googleSearch`) |303| `{"type":"computer_use","environment":"browser"}` on gemini-3.5-flash-lite | 200 completed (model answered without acting) |304| `agent:"deep-research-preview-04-2026", background:true` | 200 `{id, object, agent, status:"in_progress", created, updated, service_tier}` → `POST …/{id}/cancel` → 200 `status:"cancelled"` → GET `cancelled` → DELETE 200 |305| Environments | `GET /v1beta/environments` → 200 `{}`; `POST /v1beta/environments {}` → 200 `{id}`; `GET /v1beta/environments/{id}` → `{id, status:"active", storage:{tier:"free", project_used_bytes:"0", project_limit_bytes:"1073741824"}, file_count:"0", size_bytes:"0", created, updated}`; `GET …/files/` → 404 once, 200 `{}` once; `DELETE` → 200 `{}`; discovery spelling `GET /v1beta/environments:list` → 200 |306| Rate limiting | a burst of ~10 creates within a minute produced HTTP 429 `too_many_requests` (retry a minute later succeeded) |307308Not exercised: credentials, custom agents, triggers, webhooks, environment file upload/download, `dynamic/*`, antigravity agents.""",309    "docs/gemini/live-api.md": """Run of 2026-09-18 (`tmp-live/gemini-tools/j*.json`, `_summary.json`; examples `examples/gemini/live/*` executed; `tests/gemini/test_live.py` cheap test passes, realtime tests gated by `RUN_REALTIME_TESTS`).310311| Probe | Result |312|---|---|313| `responseModalities:["TEXT"]` on `gemini-2.5-flash-native-audio-latest`, `gemini-3.1-flash-live-preview`, `gemini-3.8-live` | **all rejected**: WebSocket close **1007** `The requested combination of response modalities (TEXT) is not supported by the model` (3.8-live first sent `setupComplete` + `sessionResumptionUpdate`, then closed) |314| `responseModalities:["AUDIO"]` + `outputAudioTranscription:{}` on `gemini-2.5-flash-native-audio-latest`, `clientContent` "Reply with OK." | `setupComplete` → `serverContent.modelTurn` parts `[{text, thought:true}]` → `serverContent.outputTranscription{text:"OK"}` → `serverContent.modelTurn` `inlineData{mimeType:"audio/pcm;rate=24000"}` × 0–6 → `serverContent.generationComplete` → `serverContent.turnComplete` **with `usageMetadata` in the same message** (`{promptTokenCount:373, responseTokenCount:4–21, responseTokensDetails:[{modality:"AUDIO"}], thoughtsTokenCount}`) |315| `sessionResumption:{}` + `contextWindowCompression:{slidingWindow:{}}` | `sessionResumptionUpdate{newHandle:<uuid>, resumable:true}` right after `setupComplete` and again after `turnComplete` |316| `setup.toolConfig` | close **1007** `Unknown name "toolConfig" at 'setup'` — tool mode cannot be forced in Live |317| `tools:[{functionDeclarations:[{…, behavior:"NON_BLOCKING"}]}]` | `toolCall{functionCalls:[{name, args, id:"function-call-11967657623789764093"}]}` after the thought part; audio continued (non-blocking) |318| SDK `client.aio.live.connect(model, config={"response_modalities":["AUDIO"], "output_audio_transcription":{}})` | sequence `server_content ×3 → server_content+usage_metadata`, transcript "OK." |319| `@google/genai` `ai.live.connect({callbacks})` | `setupComplete → modelTurn.thought → outputTranscription → generationComplete → turnComplete` |320| `POST /v1alpha/auth_tokens` and `/v1beta/auth_tokens` `{uses:1, expireTime, newSessionExpireTime}` | both 200 `{name:"auth_tokens/…"}`; with `bidiGenerateContentSetup{model, generationConfig}` lock → 200 |321| ephemeral token on `…v1alpha.GenerativeService.BidiGenerateContent` (query or header) | close **1008** `Method doesn't allow unregistered callers` |322| ephemeral token on `…v1alpha.GenerativeService.BidiGenerateContentConstrained` | **works** with `Authorization: Token <name>` header and with `?access_token=<name>`; locked token works too |323324Not exercised: realtime audio/video input, VAD tuning, interruptions, goAway timing (sessions lasted < 10 s), Live translate / transcribe models, half-cascade models (none remain).""",325    "docs/gemini/live-events.md": """Observed 2026-09-18 on `gemini-2.5-flash-native-audio-latest` (AUDIO turn, `tmp-live/gemini-tools/j1_*.json`, `j4_*.json`, `j5_ws_toolcall.json`):326327```328→ setup {model, generationConfig.responseModalities:[AUDIO], outputAudioTranscription:{}, sessionResumption:{}}329← setupComplete {}330← sessionResumptionUpdate {newHandle, resumable:true}331→ clientContent {turns:[{role:user, parts:[{text}]}], turnComplete:true}332← serverContent {modelTurn:{parts:[{text:"…", thought:true}]}}333← toolCall {functionCalls:[{name, args, id:"function-call-…"}]}            (only when a NON_BLOCKING declaration is set)334← serverContent {outputTranscription:{text:"OK"}}335← serverContent {modelTurn:{parts:[{inlineData:{mimeType:"audio/pcm;rate=24000", data}}]}}   × N336← serverContent {generationComplete:true}337← serverContent {turnComplete:true} + usageMetadata {promptTokenCount, responseTokenCount, totalTokenCount, promptTokensDetails, responseTokensDetails:[{modality:AUDIO}], thoughtsTokenCount}338← sessionResumptionUpdate {newHandle, resumable:true}339```340Rejections: `responseModalities:[TEXT]` → close 1007 on every current live model; `setup.toolConfig` → close 1007; ephemeral token on the non-Constrained method → close 1008. Not observed: `interrupted`, `goAway`, `toolCallCancellation`, `inputTranscription`, `groundingMetadata`, `voiceActivity*`.""",341}342343344def patch_docs() -> None:345    for rel, text in DOCS.items():346        p = ROOT / rel347        s = p.read_text()348        if PLACEHOLDER in s:349            s = s.replace(PLACEHOLDER, text)350            p.write_text(s)351            print("doc patched:", rel)352        else:353            print("PLACEHOLDER MISSING:", rel)354355356if __name__ == "__main__":357    merge_endpoints()358    merge_objects()359    patch_tools()360    patch_events()361    patch_matrix()362    patch_docs()363