@wenathlan/devthink 2.0.15 → 2.0.20
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.
- package/CHANGELOG.md +54 -0
- package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
- package/bridge.d.ts.map +1 -0
- package/bridge.js +1877 -0
- package/bun.d.ts.map +1 -0
- package/bun.js +52329 -0
- package/caps/background.json +712 -0
- package/caps/cli.json +360 -0
- package/caps/library.json +3611 -0
- package/caps/mcp.json +126 -0
- package/caps/pagebridge.json +377 -0
- package/caps/popup.json +51 -0
- package/caps/sidepanel.json +522 -0
- package/checksums.txt +142 -0
- package/cli.d.ts.map +1 -0
- package/{dist/cli.js → cli.js} +2 -2
- package/companion.d.ts.map +1 -0
- package/companion.js +389 -0
- package/crossbrowser.d.ts.map +1 -0
- package/crossbrowser.js +1191 -0
- package/dashdone.d.ts.map +1 -0
- package/dashdone.js +368 -0
- package/deno.d.ts.map +1 -0
- package/deno.js +52325 -0
- package/devthink.umd.js +52346 -0
- package/fixtures/bun.ts +7 -0
- package/fixtures/cjs.js +6 -0
- package/fixtures/deno.ts +6 -0
- package/fixtures/example-org-pagestate.json +22 -0
- package/fixtures/importers/automa-workflow.json +18 -0
- package/fixtures/importers/selenium-side.json +20 -0
- package/fixtures/importers/tabular-plan.csv +7 -0
- package/fixtures/importers/uivision-macro.json +10 -0
- package/fixtures/importers/v1-plan.json +13 -0
- package/fixtures/mcp-client.mjs +14 -0
- package/fixtures/pages/checkout.html +192 -0
- package/fixtures/pages/dashboard.html +110 -0
- package/fixtures/pages/feed.html +113 -0
- package/fixtures/pages/product-grid.html +150 -0
- package/fixtures/plans/form-inventory-plan.json +11 -0
- package/fixtures/plans/release-notes-plan.json +11 -0
- package/fixtures/recipes/agentscompete.json +11 -0
- package/fixtures/recipes/agentsforms.json +11 -0
- package/fixtures/recipes/agentsmonitor.json +11 -0
- package/fixtures/recipes/agentsreview.json +29 -0
- package/fixtures/recipes/agentswarm.json +11 -0
- package/fixtures/recipes/fillcalendar.json +12 -0
- package/fixtures/recipes/fillcheckout.json +13 -0
- package/fixtures/recipes/filldependentdropdowns.json +12 -0
- package/fixtures/recipes/fillfromcsv.json +13 -0
- package/fixtures/recipes/fillloginform.json +12 -0
- package/fixtures/recipes/fillmultistepwizard.json +13 -0
- package/fixtures/recipes/fillregistration.json +12 -0
- package/fixtures/recipes/fillsearchfilters.json +12 -0
- package/fixtures/recipes/filltypeahead.json +11 -0
- package/fixtures/recipes/monitoralerts.json +12 -0
- package/fixtures/recipes/monitoravailability.json +11 -0
- package/fixtures/recipes/monitornetworkquiet.json +12 -0
- package/fixtures/recipes/monitorpagechanges.json +11 -0
- package/fixtures/recipes/monitorpricechange.json +11 -0
- package/fixtures/recipes/monitorschedule.json +12 -0
- package/fixtures/recipes/scrapedynamicprices.json +13 -0
- package/fixtures/recipes/scrapeimagegallery.json +12 -0
- package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
- package/fixtures/recipes/scrapenestedlists.json +12 -0
- package/fixtures/recipes/scrapeproductgrid.json +12 -0
- package/fixtures/recipes/scrapereviews.json +12 -0
- package/fixtures/recipes/scrapesearchresults.json +12 -0
- package/fixtures/recipes/scrapetablepagination.json +12 -0
- package/fixtures/recipes/testaccessibility.json +34 -0
- package/fixtures/recipes/testconsoleerrors.json +12 -0
- package/fixtures/recipes/testdeeplinks.json +12 -0
- package/fixtures/recipes/testformvalidation.json +13 -0
- package/fixtures/recipes/testlayoutshift.json +12 -0
- package/fixtures/recipes/testlinkcheck.json +12 -0
- package/fixtures/recipes/testloadtiming.json +12 -0
- package/fixtures/recipes/testvisualdiff.json +12 -0
- package/fixtures/workflows/changelog-digest-workflow.json +13 -0
- package/fixtures/workflows/form-survey-workflow.json +13 -0
- package/gallery.json +706 -0
- package/hardening.d.ts.map +1 -0
- package/hardening.js +188 -0
- package/headless.d.ts.map +1 -0
- package/headless.js +11522 -0
- package/http.d.ts.map +1 -0
- package/http.js +2165 -0
- package/index.cjs +54682 -0
- package/index.d.ts.map +1 -0
- package/index.js +52310 -0
- package/index.neutral.js +46343 -0
- package/{web/manifest.json → manifest.json} +1 -1
- package/mcp.d.ts.map +1 -0
- package/mcp.js +20515 -0
- package/memory.d.ts.map +1 -0
- package/{memory.ts → memory.js} +2310 -3834
- package/nativehost.template.json +10 -0
- package/node.cjs +54686 -0
- package/node.d.ts.map +1 -0
- package/pack.d.ts.map +1 -0
- package/pack.js +805 -0
- package/package.json +83 -176
- package/policy.d.ts.map +1 -0
- package/{policy.ts → policy.js} +4679 -5073
- package/progress.d.ts.map +1 -0
- package/progress.js +645 -0
- package/protocol.d.ts.map +1 -0
- package/protocol.js +8334 -0
- package/schemas/audit.schema.json +226 -0
- package/schemas/capability.schema.json +113 -0
- package/schemas/envelope.schema.json +162 -0
- package/schemas/memory.schema.json +51 -0
- package/schemas/observation.schema.json +113 -0
- package/schemas/plan.schema.json +208 -0
- package/schemas/progress.schema.json +126 -0
- package/schemas/proposal.schema.json +68 -0
- package/schemas/session.schema.json +82 -0
- package/schemas/tool.schema.json +94 -0
- package/server.d.ts.map +1 -0
- package/server.js +28877 -0
- package/umd-example.html +18 -0
- package/umd.d.ts.map +1 -0
- package/accounts.ts +0 -2087
- package/agent.ts +0 -2331
- package/antigravity.ts +0 -2139
- package/apifreeze.ts +0 -4146
- package/auth.ts +0 -642
- package/background.ts +0 -16837
- package/bridge.ts +0 -2080
- package/build.ts +0 -54
- package/bun.ts +0 -26
- package/capture.ts +0 -1614
- package/checksecrets.ts +0 -72
- package/cjs.ts +0 -9
- package/cli.ts +0 -3500
- package/commands.ts +0 -1101
- package/companion.ts +0 -450
- package/compatibility.ts +0 -37
- package/config.ts +0 -3137
- package/constants.ts +0 -943
- package/core.ts +0 -1473
- package/crossbrowser.ts +0 -1359
- package/dashdone.ts +0 -605
- package/data.ts +0 -943
- package/database.ts +0 -133
- package/debug.ts +0 -850
- package/deno.ts +0 -27
- package/devthink.ts +0 -707
- package/dist/accounts.d.ts +0 -409
- package/dist/agent.d.ts +0 -837
- package/dist/antigravity.d.ts +0 -380
- package/dist/apifreeze.d.ts +0 -113
- package/dist/auth.d.ts +0 -224
- package/dist/capture.d.ts +0 -595
- package/dist/cjs.d.ts +0 -6
- package/dist/commands.d.ts +0 -310
- package/dist/compatibility.d.ts +0 -17
- package/dist/config.d.ts +0 -1853
- package/dist/constants.d.ts +0 -637
- package/dist/core.d.ts +0 -361
- package/dist/data.d.ts +0 -330
- package/dist/database.d.ts +0 -24
- package/dist/debug.d.ts +0 -270
- package/dist/engine.d.ts +0 -74
- package/dist/environments.d.ts +0 -244
- package/dist/evidence.d.ts +0 -277
- package/dist/export.d.ts +0 -311
- package/dist/fingerprint.d.ts +0 -594
- package/dist/flow.d.ts +0 -300
- package/dist/gates.d.ts +0 -103
- package/dist/identity.d.ts +0 -51
- package/dist/ids.d.ts +0 -7
- package/dist/llm.d.ts +0 -652
- package/dist/models.d.ts +0 -1807
- package/dist/net.d.ts +0 -325
- package/dist/neutral.d.ts +0 -41
- package/dist/oauth.d.ts +0 -703
- package/dist/page.d.ts +0 -1166
- package/dist/perf.d.ts +0 -481
- package/dist/plan.d.ts +0 -134
- package/dist/project.d.ts +0 -395
- package/dist/providers.d.ts +0 -37
- package/dist/quota.d.ts +0 -453
- package/dist/recovery.d.ts +0 -621
- package/dist/request.d.ts +0 -1492
- package/dist/run.d.ts +0 -779
- package/dist/runtime.d.ts +0 -212
- package/dist/search.d.ts +0 -375
- package/dist/security.d.ts +0 -534
- package/dist/serve.d.ts +0 -136
- package/dist/session.d.ts +0 -296
- package/dist/storage.d.ts +0 -53
- package/dist/streaming.d.ts +0 -1119
- package/dist/swarm.d.ts +0 -328
- package/dist/tools.d.ts +0 -170
- package/dist/types.d.ts +0 -8357
- package/dist/utils.d.ts +0 -82
- package/dist/version.d.ts +0 -7
- package/dist/versionregistry.d.ts +0 -406
- package/dist/views.d.ts +0 -579
- package/dist/workbenchsession.d.ts +0 -69
- package/dist/workflow.d.ts +0 -649
- package/docs/readme.md +0 -92
- package/docs/schemas/antigravity.json +0 -192
- package/engine.ts +0 -1524
- package/environments.ts +0 -1005
- package/evidence.ts +0 -643
- package/export.ts +0 -889
- package/fingerprint.ts +0 -1458
- package/flow.ts +0 -1190
- package/gates.ts +0 -307
- package/gateway.ts +0 -34
- package/hardening.ts +0 -191
- package/headless.ts +0 -369
- package/http.ts +0 -2110
- package/identity.ts +0 -246
- package/ids.ts +0 -26
- package/index.ts +0 -369
- package/llm.ts +0 -2682
- package/mcp.ts +0 -1835
- package/models.ts +0 -3805
- package/modes.ts +0 -43
- package/net.ts +0 -1214
- package/neutral.ts +0 -362
- package/node.ts +0 -26
- package/oauth.ts +0 -2216
- package/offscreen.ts +0 -170
- package/pack.ts +0 -956
- package/page.ts +0 -7824
- package/pagebridge.ts +0 -1116
- package/perf.ts +0 -957
- package/plan.ts +0 -672
- package/plugin.ts +0 -76
- package/prisma.config.ts +0 -27
- package/progress.ts +0 -1308
- package/project.ts +0 -1291
- package/protocol.ts +0 -4187
- package/providers.ts +0 -246
- package/quota.ts +0 -1887
- package/recovery.ts +0 -2433
- package/request.ts +0 -4482
- package/run.ts +0 -2135
- package/runtime.ts +0 -526
- package/search.ts +0 -1760
- package/security.ts +0 -1673
- package/serve.ts +0 -419
- package/server.ts +0 -5538
- package/session.ts +0 -1162
- package/storage.ts +0 -182
- package/streaming.ts +0 -4101
- package/swarm.ts +0 -884
- package/sync.ts +0 -56
- package/tests/agentcert.test.ts +0 -128
- package/tests/agentfleet.test.ts +0 -214
- package/tests/agentreview.test.ts +0 -241
- package/tests/agentstream.test.ts +0 -335
- package/tests/agentwork.test.ts +0 -356
- package/tests/apifreeze.test.ts +0 -290
- package/tests/apimap.test.ts +0 -112
- package/tests/approvalgate.test.ts +0 -175
- package/tests/artifactmanifest.test.ts +0 -204
- package/tests/attentionfeed.test.ts +0 -163
- package/tests/backgroundruns.test.ts +0 -100
- package/tests/batchscheduling.test.ts +0 -131
- package/tests/blackboard.test.ts +0 -238
- package/tests/bridge.test.ts +0 -1088
- package/tests/browsercoverage.test.ts +0 -134
- package/tests/browserpolyfills.test.ts +0 -124
- package/tests/build.mjs +0 -1260
- package/tests/clientauth.test.ts +0 -223
- package/tests/clitools.test.ts +0 -578
- package/tests/confirmgates.test.ts +0 -190
- package/tests/containerpack.test.ts +0 -143
- package/tests/controlflow.test.ts +0 -2120
- package/tests/coordination.test.ts +0 -345
- package/tests/copyscreen.test.ts +0 -139
- package/tests/core.test.ts +0 -417
- package/tests/costcert.test.ts +0 -95
- package/tests/crossbrowser.test.ts +0 -115
- package/tests/cspaudit.test.ts +0 -79
- package/tests/dashdone.test.ts +0 -615
- package/tests/datagrid.test.ts +0 -137
- package/tests/doccheck.test.ts +0 -107
- package/tests/dombatch.test.ts +0 -109
- package/tests/emulation.test.ts +0 -282
- package/tests/environments.test.ts +0 -259
- package/tests/evidenceviews.test.ts +0 -88
- package/tests/exporttools.test.ts +0 -185
- package/tests/firefoxprep.test.ts +0 -134
- package/tests/flowlibrary.test.ts +0 -242
- package/tests/flowrun.test.ts +0 -303
- package/tests/forensics.test.ts +0 -439
- package/tests/gateway.test.ts +0 -939
- package/tests/headlesslib.test.ts +0 -171
- package/tests/httpstream.test.ts +0 -277
- package/tests/immutablelog.test.ts +0 -157
- package/tests/inboundguard.test.ts +0 -177
- package/tests/lazyload.test.ts +0 -72
- package/tests/librarymodes.test.ts +0 -623
- package/tests/llm.test.ts +0 -696
- package/tests/manifest.test.ts +0 -226
- package/tests/maskinputs.test.ts +0 -150
- package/tests/matrixverify.test.ts +0 -111
- package/tests/mavenpack.test.ts +0 -75
- package/tests/mcpserver.test.ts +0 -704
- package/tests/memory.test.ts +0 -4155
- package/tests/memorycare.test.ts +0 -230
- package/tests/migrateplan.test.ts +0 -349
- package/tests/minimization.test.ts +0 -414
- package/tests/modelroute.test.ts +0 -112
- package/tests/multiagent.test.ts +0 -440
- package/tests/nativebridge.test.ts +0 -807
- package/tests/navigation.test.ts +0 -284
- package/tests/nugetpack.test.ts +0 -83
- package/tests/orchestration.test.ts +0 -449
- package/tests/originpolicy.test.ts +0 -405
- package/tests/outputcompare.test.ts +0 -97
- package/tests/package.test.ts +0 -91
- package/tests/pagebridge.test.ts +0 -5909
- package/tests/pentest.test.ts +0 -75
- package/tests/perfrecords.test.ts +0 -257
- package/tests/permdiff.test.ts +0 -85
- package/tests/phishguard.test.ts +0 -128
- package/tests/pickerviews.test.ts +0 -155
- package/tests/pipelines.test.ts +0 -381
- package/tests/planlint.test.ts +0 -240
- package/tests/planreview.test.ts +0 -383
- package/tests/platformtargets.test.ts +0 -56
- package/tests/policy.test.ts +0 -8284
- package/tests/portability.test.ts +0 -96
- package/tests/progress.test.ts +0 -1036
- package/tests/promptlibrary.test.ts +0 -116
- package/tests/protocol.test.ts +0 -4793
- package/tests/publishingpipeline.test.ts +0 -314
- package/tests/quickactions.test.ts +0 -164
- package/tests/readiness.test.ts +0 -78
- package/tests/recipes.test.ts +0 -163
- package/tests/redactshots.test.ts +0 -216
- package/tests/registernvidiakeys.ts +0 -51
- package/tests/relayserve.test.ts +0 -304
- package/tests/releasenotes.test.ts +0 -276
- package/tests/resilience.test.ts +0 -467
- package/tests/resourceaware.test.ts +0 -187
- package/tests/runbudget.test.ts +0 -125
- package/tests/runhistory.test.ts +0 -299
- package/tests/runreplay.test.ts +0 -113
- package/tests/runresume.test.ts +0 -184
- package/tests/runstate.test.ts +0 -288
- package/tests/runtimeadapters.test.ts +0 -251
- package/tests/safariskeleton.test.ts +0 -115
- package/tests/sandboxframe.test.ts +0 -196
- package/tests/sbom.test.ts +0 -105
- package/tests/secretvault.test.ts +0 -188
- package/tests/selcache.test.ts +0 -111
- package/tests/sessioninterface.test.ts +0 -791
- package/tests/sessions.test.ts +0 -591
- package/tests/siteprefs.test.ts +0 -228
- package/tests/snapshotdelta.test.ts +0 -165
- package/tests/statusviews.test.ts +0 -237
- package/tests/stepmeter.test.ts +0 -200
- package/tests/streamparse.test.ts +0 -163
- package/tests/stylepruning.test.ts +0 -90
- package/tests/surfaces.test.ts +0 -282
- package/tests/sweep.test.ts +0 -78
- package/tests/syncbridge.test.ts +0 -164
- package/tests/taskqueue.test.ts +0 -206
- package/tests/toolcalls.test.ts +0 -336
- package/tests/toolcatalog.test.ts +0 -201
- package/tests/torture-agent.test.ts +0 -706
- package/tests/torture-auth.test.ts +0 -461
- package/tests/torture-commands.test.ts +0 -895
- package/tests/torture-data.test.ts +0 -923
- package/tests/torture-environments.test.ts +0 -285
- package/tests/torture-export.test.ts +0 -263
- package/tests/torture-http.test.ts +0 -1379
- package/tests/torture-llm.test.ts +0 -808
- package/tests/torture-memory.test.ts +0 -1084
- package/tests/torture-plan.test.ts +0 -829
- package/tests/torture-run.test.ts +0 -626
- package/tests/torture-session.test.ts +0 -928
- package/tests/torture-swarm.test.ts +0 -1245
- package/tests/torture-tools.test.ts +0 -964
- package/tests/torture-workflow.test.ts +0 -2354
- package/tests/tourviews.test.ts +0 -52
- package/tests/transparency.test.ts +0 -147
- package/tests/trigger.test.ts +0 -941
- package/tests/v1sunset.test.ts +0 -428
- package/tests/virtlist.test.ts +0 -94
- package/tests/vision.test.ts +0 -540
- package/tests/vsixpack.test.ts +0 -145
- package/tests/webapi.test.ts +0 -523
- package/tests/workflow.test.ts +0 -1785
- package/tests/workfloweditor.test.ts +0 -759
- package/tests/xpipack.test.ts +0 -128
- package/tools.ts +0 -734
- package/types.ts +0 -9908
- package/umd.ts +0 -9
- package/utils.ts +0 -249
- package/version.ts +0 -8
- package/versionregistry.ts +0 -1310
- package/views.ts +0 -2153
- package/vitest.config.ts +0 -12
- package/web/App.tsx +0 -78
- package/web/README.md +0 -47
- package/web/capacitor.config.ts +0 -46
- package/web/const.ts +0 -19
- package/web/control.shell.tsx +0 -66
- package/web/dashboardpage.ts +0 -554
- package/web/db.ts +0 -493
- package/web/design.html +0 -541
- package/web/gatewayview/Gateway.tsx +0 -345
- package/web/gatewayview/config.ts +0 -1042
- package/web/gatewayview/definition.ts +0 -174
- package/web/icons.ts +0 -15
- package/web/optionspage.ts +0 -691
- package/web/popup.ts +0 -1651
- package/web/schema.prisma +0 -176
- package/web/sidepanel.ts +0 -7941
- package/web/sitemanifest.json +0 -13
- package/web/terminal-ui.tsx +0 -622
- package/web/transparencypage.ts +0 -541
- package/web/vite.config.ts +0 -39
- package/web/workspace.ts +0 -23
- package/workbenchmemory.ts +0 -104
- package/workbenchsession.ts +0 -304
- package/workflow.ts +0 -4495
- /package/{dist/bun.d.ts → bun.d.ts} +0 -0
- /package/{dist/cli.d.ts → cli.d.ts} +0 -0
- /package/{dist/companion.d.ts → companion.d.ts} +0 -0
- /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
- /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
- /package/{dist/deno.d.ts → deno.d.ts} +0 -0
- /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
- /package/{dist/headless.d.ts → headless.d.ts} +0 -0
- /package/{dist/http.d.ts → http.d.ts} +0 -0
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
- /package/{dist/memory.d.ts → memory.d.ts} +0 -0
- /package/{dist/node.d.ts → node.d.ts} +0 -0
- /package/{dist/pack.d.ts → pack.d.ts} +0 -0
- /package/{dist/policy.d.ts → policy.d.ts} +0 -0
- /package/{dist/progress.d.ts → progress.d.ts} +0 -0
- /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
- /package/{dist/server.d.ts → server.d.ts} +0 -0
- /package/{dist/umd.d.ts → umd.d.ts} +0 -0
package/search.ts
DELETED
|
@@ -1,1760 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file search.ts
|
|
3
|
-
* @module provider/search
|
|
4
|
-
* @description
|
|
5
|
-
* Third-person observer: the library watches how Antigravity Manager and
|
|
6
|
-
* Gemini CLI perform grounding via googleSearch + urlContext and reproduces
|
|
7
|
-
* the contract without relying on localhost v1 proxy. The observer calls
|
|
8
|
-
* cloudcode-pa.googleapis.com directly, cascades endpoints on 403/404/5xx,
|
|
9
|
-
* injects native Gemini tools, and parses groundingMetadata rigorously.
|
|
10
|
-
*
|
|
11
|
-
* Layout: library-first, root-first — file lives beside its siblings
|
|
12
|
-
* Runtime: only node:* builtins + global fetch (Node >=18). Zero external deps.
|
|
13
|
-
* Date governance: 25/08/2026 — Canto do Buriti, PI, BR — version awareness 2.0.0
|
|
14
|
-
*
|
|
15
|
-
* Contract:
|
|
16
|
-
* - Endpoint: https://cloudcode-pa.googleapis.com/v1internal:generateContent (primary)
|
|
17
|
-
* cascade: daily-cloudcode-pa.googleapis.com, sandbox fallback
|
|
18
|
-
* - No localhost v1 base-url proxy, direct cloudcode-pa (security + user choice)
|
|
19
|
-
* - Native tools: googleSearch + urlContext — both injected when enabled
|
|
20
|
-
* - Model: SEARCH_MODEL gemini-3-flash with preview fallback chain
|
|
21
|
-
* - Thinking: thinkingLevel → thinkingBudget mapping (8192/16384/32768) clamped 32k
|
|
22
|
-
* - Grounding parse: groundingMetadata.webSearchQueries, groundingChunks[].web.uri/title,
|
|
23
|
-
* groundingSupports, searchEntryPoint.renderedContent
|
|
24
|
-
* - Proxy support: env HTTPS_PROXY/HTTP_PROXY/NO_PROXY + options.proxyUrl
|
|
25
|
-
* manual CONNECT tunneling via node:net + node:tls — no external deps
|
|
26
|
-
* - Bypass metadata: mimics antigravity/{ver} {os}/{arch} UA, strips x-goog-user-project,
|
|
27
|
-
* jitter 0-80ms, FNV-1a deterministic session_id note (not needed here but coherent)
|
|
28
|
-
*
|
|
29
|
-
* EN: Antigravity bypass relies on mimicking antigravity/{ver} {os}/{arch} UA
|
|
30
|
-
* and bypassing x-goog-user-project stripping, endpoint cascade 403/404/5xx.
|
|
31
|
-
* PT-BR: Bypass Antigravity depende de imitar UA antigravity/{ver} {os}/{arch}
|
|
32
|
-
* e remover x-goog-user-project, cascata de endpoints em 403/404/5xx.
|
|
33
|
-
*
|
|
34
|
-
* Production-ready: JSDoc, exhaustive error handling, timeout, abort, proxy,
|
|
35
|
-
* endpoint cascade, thinking budget, grounding parsing, no placeholder.
|
|
36
|
-
*
|
|
37
|
-
* @author devthink
|
|
38
|
-
* @license MIT
|
|
39
|
-
* @version 2.0.0
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
import * as os from "node:os";
|
|
43
|
-
import { platform as osPlatform, arch as osArch, release as osRelease } from "node:os";
|
|
44
|
-
import * as net from "node:net";
|
|
45
|
-
import * as tls from "node:tls";
|
|
46
|
-
import * as http from "node:http";
|
|
47
|
-
import * as https from "node:https";
|
|
48
|
-
import { createHash } from "node:crypto";
|
|
49
|
-
import { join as pathJoin } from "node:path";
|
|
50
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Frozen-owner imports — identical values owned by constants.ts / core.ts /
|
|
54
|
-
// models.ts, re-exported below so the module surface stays unchanged.
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
import {
|
|
58
|
-
ANTIGRAVITY_USER_AGENT_FALLBACK,
|
|
59
|
-
ANTIGRAVITY_VERSION_FALLBACK,
|
|
60
|
-
DEFAULT_TIMEOUT_MS,
|
|
61
|
-
GENERATE_CONTENT_ENDPOINT,
|
|
62
|
-
JITTER_MAX_MS,
|
|
63
|
-
JITTER_MIN_MS,
|
|
64
|
-
PLUGIN_VERSION,
|
|
65
|
-
STREAM_GENERATE_ENDPOINT,
|
|
66
|
-
FETCH_TIMEOUT_MS,
|
|
67
|
-
} from "./constants.js";
|
|
68
|
-
import { fnv1a32, getJitter, isRetryableStatus } from "./core.js";
|
|
69
|
-
import {
|
|
70
|
-
buildAntagravityHeaders,
|
|
71
|
-
getDynamicUserAgent,
|
|
72
|
-
getXGoogApiClient,
|
|
73
|
-
normalizeArch,
|
|
74
|
-
normalizePlatform,
|
|
75
|
-
} from "./fingerprint.js";
|
|
76
|
-
// v2.1.15 Phase A: model-identification families (search/default model ids,
|
|
77
|
-
// thinking tables, image-gen helpers) are imported from models.ts — THE
|
|
78
|
-
// single owner — instead of the former constants.js re-exports.
|
|
79
|
-
import {
|
|
80
|
-
CODE_ASSIST_ENDPOINTS,
|
|
81
|
-
SEARCH_MODEL,
|
|
82
|
-
SEARCH_MODEL_FALLBACKS,
|
|
83
|
-
SEARCH_MODEL_PREVIEW,
|
|
84
|
-
THINKING_BUDGET_MAP,
|
|
85
|
-
THINKING_BUDGET_MAX_CLAUDE,
|
|
86
|
-
THINKING_BUDGET_MAX_GEMINI,
|
|
87
|
-
THINKING_LEVELS,
|
|
88
|
-
buildImageGenConfig,
|
|
89
|
-
isImageGenModel,
|
|
90
|
-
} from "./models.js";
|
|
91
|
-
import type { ThinkingLevel } from "./models.js";
|
|
92
|
-
// v2.1.15 Phase A: parseGrounding moved from core.ts — web-citation
|
|
93
|
-
// extraction is search-grounding domain logic.
|
|
94
|
-
function parseGrounding(meta: any): { uri: string; title?: string }[] {
|
|
95
|
-
const chunks = meta?.groundingChunks || meta?.grounding_chunks || [];
|
|
96
|
-
return chunks.map((c: any) => (c.web ? { uri: c.web.uri, title: c.web.title } : null)).filter(Boolean);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export {
|
|
100
|
-
ANTIGRAVITY_USER_AGENT_FALLBACK,
|
|
101
|
-
ANTIGRAVITY_VERSION_FALLBACK,
|
|
102
|
-
CODE_ASSIST_ENDPOINTS,
|
|
103
|
-
DEFAULT_TIMEOUT_MS,
|
|
104
|
-
GENERATE_CONTENT_ENDPOINT,
|
|
105
|
-
JITTER_MAX_MS,
|
|
106
|
-
JITTER_MIN_MS,
|
|
107
|
-
SEARCH_MODEL,
|
|
108
|
-
SEARCH_MODEL_FALLBACKS,
|
|
109
|
-
SEARCH_MODEL_PREVIEW,
|
|
110
|
-
STREAM_GENERATE_ENDPOINT,
|
|
111
|
-
THINKING_BUDGET_MAP,
|
|
112
|
-
THINKING_BUDGET_MAX_CLAUDE,
|
|
113
|
-
THINKING_BUDGET_MAX_GEMINI,
|
|
114
|
-
THINKING_LEVELS,
|
|
115
|
-
buildImageGenConfig,
|
|
116
|
-
getDynamicUserAgent,
|
|
117
|
-
getJitter,
|
|
118
|
-
isImageGenModel,
|
|
119
|
-
};
|
|
120
|
-
export type { ThinkingLevel };
|
|
121
|
-
export { parseGrounding };
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Constants — observer notes prod is authoritative
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
/** Endpoint cascade — observer watches traffic: PROD first, then DAILY, SANDBOX */
|
|
128
|
-
export const DEFAULT_ENDPOINT_ORDER: readonly string[] = [
|
|
129
|
-
CODE_ASSIST_ENDPOINTS.PROD,
|
|
130
|
-
CODE_ASSIST_ENDPOINTS.DAILY,
|
|
131
|
-
CODE_ASSIST_ENDPOINTS.SANDBOX,
|
|
132
|
-
] as const;
|
|
133
|
-
|
|
134
|
-
/** Timeouts — observer notes 10s per control call, 60s for content */
|
|
135
|
-
export const SEARCH_TIMEOUT_MS = 60_000 as const;
|
|
136
|
-
// local variant: diverges from models (models.FETCH_TIMEOUT_MS is the 10s control-plane timeout; this file pins it to the 60s content timeout)
|
|
137
|
-
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
// Types — grounding contract from Gemini API
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Web chunk returned by groundingMetadata.
|
|
144
|
-
* Observer documents: each chunk points to a source URL used for grounding.
|
|
145
|
-
*/
|
|
146
|
-
export interface WebGroundingChunk {
|
|
147
|
-
/** source uri, e.g. https://developer.mozilla.org/... */
|
|
148
|
-
uri: string;
|
|
149
|
-
/** title returned by search */
|
|
150
|
-
title: string;
|
|
151
|
-
/** derived domain — convenience */
|
|
152
|
-
domain?: string | undefined;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Raw web object inside groundingChunks */
|
|
156
|
-
export interface GroundingChunkWeb {
|
|
157
|
-
uri?: string;
|
|
158
|
-
title?: string;
|
|
159
|
-
domain?: string;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export interface GroundingChunk {
|
|
163
|
-
web?: GroundingChunkWeb;
|
|
164
|
-
/** alternative shapes: retrievedContext etc. — kept for resilience */
|
|
165
|
-
retrievedContext?: { uri?: string; title?: string; text?: string };
|
|
166
|
-
[k: string]: unknown;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export interface GroundingSupportSegment {
|
|
170
|
-
startIndex?: number;
|
|
171
|
-
endIndex?: number;
|
|
172
|
-
text?: string;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface GroundingSupport {
|
|
176
|
-
segment?: GroundingSupportSegment;
|
|
177
|
-
groundingChunkIndices?: number[];
|
|
178
|
-
confidenceScores?: number[];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export interface SearchEntryPoint {
|
|
182
|
-
renderedContent?: string;
|
|
183
|
-
[k: string]: unknown;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface GroundingMetadata {
|
|
187
|
-
webSearchQueries?: string[];
|
|
188
|
-
groundingChunks?: GroundingChunk[];
|
|
189
|
-
groundingSupports?: GroundingSupport[];
|
|
190
|
-
searchEntryPoint?: SearchEntryPoint;
|
|
191
|
-
retrievalQueries?: string[];
|
|
192
|
-
[k: string]: unknown;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Request shape */
|
|
196
|
-
export interface ExecuteSearchInput {
|
|
197
|
-
/** user query to ground with googleSearch/urlContext */
|
|
198
|
-
query: string;
|
|
199
|
-
/** optional system instruction — helps guide grounded answer */
|
|
200
|
-
systemPrompt?: string | undefined;
|
|
201
|
-
/** optional additional context appended to user query for better grounding */
|
|
202
|
-
additionalContext?: string | undefined;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export interface ExecuteSearchAuth {
|
|
206
|
-
/** OAuth access_token Bearer — required */
|
|
207
|
-
accessToken: string;
|
|
208
|
-
/** project id resolved via loadCodeAssist/onboardUser — optional but improves success */
|
|
209
|
-
projectId?: string;
|
|
210
|
-
/** optional custom user agent — fallback uses dynamic UA */
|
|
211
|
-
userAgent?: string;
|
|
212
|
-
/** optional antigravity version for UA — defaults to 1.19.2 */
|
|
213
|
-
version?: string;
|
|
214
|
-
/** optional client metadata override */
|
|
215
|
-
clientMetadata?: string;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export interface ExecuteSearchOptions {
|
|
219
|
-
/** model — defaults to SEARCH_MODEL gemini-3-flash */
|
|
220
|
-
model?: string;
|
|
221
|
-
/** thinking level mapping to budget */
|
|
222
|
-
thinkingLevel?: ThinkingLevel;
|
|
223
|
-
/** direct budget override — clamped 0-32768 */
|
|
224
|
-
thinkingBudget?: number;
|
|
225
|
-
/** include groundingMetadata in result — default true */
|
|
226
|
-
includeGrounding?: boolean;
|
|
227
|
-
/** inject url_context companion tool — default true per spec */
|
|
228
|
-
useUrlContext?: boolean;
|
|
229
|
-
/** inject google_search tool — default true */
|
|
230
|
-
useGoogleSearch?: boolean;
|
|
231
|
-
/** proxy url e.g. http://127.0.0.1:8080 — null disables env proxy */
|
|
232
|
-
proxyUrl?: string | null;
|
|
233
|
-
/** timeout per fetch — default 60s */
|
|
234
|
-
timeoutMs?: number;
|
|
235
|
-
/** custom endpoint list for cascade — default PROD/DAILY/SANDBOX */
|
|
236
|
-
endpoints?: string[];
|
|
237
|
-
/** include rendered search entry point? */
|
|
238
|
-
includeSearchEntryPoint?: boolean;
|
|
239
|
-
/** temperature for grounded answer — default 0.2 */
|
|
240
|
-
temperature?: number;
|
|
241
|
-
/** max output tokens — default 8192 */
|
|
242
|
-
maxOutputTokens?: number;
|
|
243
|
-
/** debug logging */
|
|
244
|
-
debug?: boolean;
|
|
245
|
-
/** jitter between endpoint retries — default true */
|
|
246
|
-
jitter?: boolean;
|
|
247
|
-
/** max retries across endpoints — internal */
|
|
248
|
-
maxRetries?: number;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Final grounded result */
|
|
252
|
-
export interface SearchResult {
|
|
253
|
-
/** synthesized answer text */
|
|
254
|
-
text: string;
|
|
255
|
-
/** original query */
|
|
256
|
-
query: string;
|
|
257
|
-
/** model used */
|
|
258
|
-
model: string;
|
|
259
|
-
/** true if grounding sources returned */
|
|
260
|
-
grounded: boolean;
|
|
261
|
-
/** queries Gemini issued to Google Search */
|
|
262
|
-
webSearchQueries: string[];
|
|
263
|
-
/** parsed web sources — uri/title */
|
|
264
|
-
groundingChunks: WebGroundingChunk[];
|
|
265
|
-
/** raw supports — segment attribution */
|
|
266
|
-
groundingSupports?: GroundingSupport[] | undefined;
|
|
267
|
-
/** rendered search entry point HTML if present */
|
|
268
|
-
searchEntryPoint?: SearchEntryPoint | undefined;
|
|
269
|
-
/** raw groundingMetadata as returned */
|
|
270
|
-
groundingMetadata?: GroundingMetadata | undefined;
|
|
271
|
-
/** raw candidate response for debugging */
|
|
272
|
-
raw?: unknown | undefined;
|
|
273
|
-
/** endpoint that succeeded */
|
|
274
|
-
endpointUsed?: string | undefined;
|
|
275
|
-
/** thinking budget used */
|
|
276
|
-
thinkingBudgetUsed?: number | undefined;
|
|
277
|
-
/** was fallback project used? */
|
|
278
|
-
isFallbackProject?: boolean;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ---------------------------------------------------------------------------
|
|
282
|
-
// Error — production-ready with status context
|
|
283
|
-
// ---------------------------------------------------------------------------
|
|
284
|
-
|
|
285
|
-
export class SearchError extends Error {
|
|
286
|
-
public readonly status?: number | undefined;
|
|
287
|
-
public readonly endpoint?: string | undefined;
|
|
288
|
-
public readonly retryable: boolean;
|
|
289
|
-
public readonly code?: string | undefined;
|
|
290
|
-
|
|
291
|
-
constructor(
|
|
292
|
-
message: string,
|
|
293
|
-
opts: { status?: number; endpoint?: string; retryable?: boolean; code?: string; cause?: unknown } = {},
|
|
294
|
-
) {
|
|
295
|
-
super(message);
|
|
296
|
-
this.name = "SearchError";
|
|
297
|
-
this.status = opts.status;
|
|
298
|
-
this.endpoint = opts.endpoint;
|
|
299
|
-
this.retryable = opts.retryable ?? false;
|
|
300
|
-
this.code = opts.code;
|
|
301
|
-
if (opts.cause) (this as any).cause = opts.cause;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ---------------------------------------------------------------------------
|
|
306
|
-
// Observer helpers — third-person, coherent with fingerprint.ts / project.ts
|
|
307
|
-
// ---------------------------------------------------------------------------
|
|
308
|
-
|
|
309
|
-
// v2.1.16: the local normalizePlatform/normalizeArch copies were deleted —
|
|
310
|
-
// byte-identical (every branch) to the fingerprint.ts exports, which are now
|
|
311
|
-
// imported above (search is L4, fingerprint is L2 — downward import, allowed).
|
|
312
|
-
|
|
313
|
-
// getDynamicUserAgent is imported from constants.js (identical signature and
|
|
314
|
-
// outputs; the owner's trim placement differs but the fallback constant carries
|
|
315
|
-
// no whitespace, so results match for every input) and re-exported above.
|
|
316
|
-
|
|
317
|
-
// v2.1.16: the stale hardcoded pluginVersion literal (2.1.14) was replaced
|
|
318
|
-
// by the constants.js PLUGIN_VERSION import — the version-envelope owner — so
|
|
319
|
-
// the Client-Metadata header stops drifting behind the release version. The
|
|
320
|
-
// ANTIGRAVITY ideType/ideVersion/osVersion/arch fields stay local (the
|
|
321
|
-
// fingerprint.ts owner builder derives them differently).
|
|
322
|
-
function buildClientMetadata(version?: string): string {
|
|
323
|
-
const ver = version ?? ANTIGRAVITY_VERSION_FALLBACK;
|
|
324
|
-
const plat = normalizePlatform(osPlatform()).toUpperCase();
|
|
325
|
-
const archUpper = normalizeArch(osArch()).toUpperCase();
|
|
326
|
-
const parts = [
|
|
327
|
-
`ideType=ANTIGRAVITY`,
|
|
328
|
-
`platform=${plat}`,
|
|
329
|
-
`ideVersion=${ver}`,
|
|
330
|
-
`pluginVersion=${PLUGIN_VERSION}`,
|
|
331
|
-
`osVersion=${osRelease()}`,
|
|
332
|
-
`arch=${archUpper}`,
|
|
333
|
-
];
|
|
334
|
-
return parts.join(",");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// getXGoogApiClient is imported from constants.js (byte-identical body:
|
|
338
|
-
// `antigravity/{ver} gl-node/{node} gax/4.9.0 grpc/1.14.0`).
|
|
339
|
-
|
|
340
|
-
// local variant: diverges from models (models.buildAntagravityHeaders takes an optional UA, omits X-Goog-Api-Client/Client-Metadata and strips FORBIDDEN_HEADERS; this variant requires the UA, emits both bypass headers and strips only x-goog-user-project)
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Builds Antigravity header map for cloudcode-pa.
|
|
344
|
-
* Observer notes: AM only sends User-Agent on content requests, not X-Goog-Api-Client
|
|
345
|
-
* for onboardUser/loadCodeAssist, but for generateContent it sends both.
|
|
346
|
-
* Also strips x-goog-user-project to avoid 403 IAM (requirement).
|
|
347
|
-
*
|
|
348
|
-
* @param accessToken - bearer
|
|
349
|
-
* @param userAgent - UA string
|
|
350
|
-
* @param extra - extra headers
|
|
351
|
-
*/
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* FNV-1a 32-bit — deterministic hashing for logs, not secrets.
|
|
355
|
-
*/
|
|
356
|
-
// local variant: diverges from core (uses plain multiplication `(hash * 0x01000193) >>> 0` instead of Math.imul; floats lose low bits so results differ from core/models for most inputs)
|
|
357
|
-
|
|
358
|
-
function hashToken(token: string): string {
|
|
359
|
-
try {
|
|
360
|
-
return createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
361
|
-
} catch {
|
|
362
|
-
return fnv1a32(token).toString(16);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Jitter 0-80ms anti-rate-limit — observed in AM.
|
|
368
|
-
* Identical to constants.getJitter — imported from the frozen owner and
|
|
369
|
-
* re-exported above.
|
|
370
|
-
*/
|
|
371
|
-
|
|
372
|
-
function sleep(ms: number): Promise<void> {
|
|
373
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Checks if HTTP status is retryable for endpoint cascade.
|
|
378
|
-
* Observer: 403 project IAM mismatch, 404 rollout, 5xx instability → try next endpoint.
|
|
379
|
-
*/
|
|
380
|
-
// local variant: diverges from models (models.isRetryableStatus also treats 408 as retryable; this cascade variant does not)
|
|
381
|
-
|
|
382
|
-
// ---------------------------------------------------------------------------
|
|
383
|
-
// Proxy support — only node:* builtins + fetch
|
|
384
|
-
// ---------------------------------------------------------------------------
|
|
385
|
-
|
|
386
|
-
export interface ProxyResolution {
|
|
387
|
-
/** final proxy url to use, null if disabled */
|
|
388
|
-
proxyUrl: string | null;
|
|
389
|
-
/** reason / source for debugging */
|
|
390
|
-
source: "option" | "env_https_proxy" | "env_http_proxy" | "env_all_proxy" | "disabled" | "none" | "no_proxy_bypass";
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Parses NO_PROXY env — comma separated list of host suffixes, IPs, CIDRs (simplified).
|
|
395
|
-
*/
|
|
396
|
-
export function isNoProxyMatch(targetHost: string, noProxyRaw: string | undefined): boolean {
|
|
397
|
-
if (!noProxyRaw) return false;
|
|
398
|
-
const list = noProxyRaw
|
|
399
|
-
.split(",")
|
|
400
|
-
.map((s) => s.trim().toLowerCase())
|
|
401
|
-
.filter(Boolean);
|
|
402
|
-
if (list.length === 0) return false;
|
|
403
|
-
const host = targetHost.toLowerCase();
|
|
404
|
-
for (const pattern of list) {
|
|
405
|
-
if (pattern === "*" || pattern === "*.*") return true;
|
|
406
|
-
if (pattern === host) return true;
|
|
407
|
-
if (pattern.startsWith(".")) {
|
|
408
|
-
// .example.com matches foo.example.com
|
|
409
|
-
if (host.endsWith(pattern)) return true;
|
|
410
|
-
}
|
|
411
|
-
if (host.endsWith(`.${pattern}`)) return true;
|
|
412
|
-
if (pattern.startsWith("*.")) {
|
|
413
|
-
const suffix = pattern.slice(2);
|
|
414
|
-
if (host === suffix || host.endsWith(`.${suffix}`)) return true;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Resolves proxy URL respecting options.proxyUrl, env vars, and NO_PROXY.
|
|
422
|
-
* Observer scans env case-insensitively as per curl conventions.
|
|
423
|
-
*
|
|
424
|
-
* @param optionsProxy - explicit option, string or null (null disables)
|
|
425
|
-
* @param targetUrl - target to check NO_PROXY against
|
|
426
|
-
*/
|
|
427
|
-
export function resolveProxyUrl(optionsProxy: string | null | undefined, targetUrl?: string): ProxyResolution {
|
|
428
|
-
// explicit null disables proxy even if env present
|
|
429
|
-
if (optionsProxy === null) {
|
|
430
|
-
return { proxyUrl: null, source: "disabled" };
|
|
431
|
-
}
|
|
432
|
-
if (typeof optionsProxy === "string" && optionsProxy.trim().length > 0) {
|
|
433
|
-
return { proxyUrl: optionsProxy.trim(), source: "option" };
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// env var resolution — order HTTPS_PROXY > HTTP_PROXY > ALL_PROXY
|
|
437
|
-
const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy;
|
|
438
|
-
const envHttp = process.env.HTTP_PROXY || process.env.http_proxy;
|
|
439
|
-
const envAll = process.env.ALL_PROXY || process.env.all_proxy;
|
|
440
|
-
const noProxyRaw = process.env.NO_PROXY || process.env.no_proxy;
|
|
441
|
-
|
|
442
|
-
if (targetUrl) {
|
|
443
|
-
try {
|
|
444
|
-
const u = new URL(targetUrl);
|
|
445
|
-
if (isNoProxyMatch(u.hostname, noProxyRaw)) {
|
|
446
|
-
return { proxyUrl: null, source: "no_proxy_bypass" };
|
|
447
|
-
}
|
|
448
|
-
} catch {
|
|
449
|
-
// ignore malformed target
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (envHttps && envHttps.trim()) {
|
|
454
|
-
return { proxyUrl: envHttps.trim(), source: "env_https_proxy" };
|
|
455
|
-
}
|
|
456
|
-
if (envHttp && envHttp.trim()) {
|
|
457
|
-
return { proxyUrl: envHttp.trim(), source: "env_http_proxy" };
|
|
458
|
-
}
|
|
459
|
-
if (envAll && envAll.trim()) {
|
|
460
|
-
return { proxyUrl: envAll.trim(), source: "env_all_proxy" };
|
|
461
|
-
}
|
|
462
|
-
return { proxyUrl: null, source: "none" };
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Builds Proxy-Authorization header if proxy URL contains credentials.
|
|
467
|
-
*/
|
|
468
|
-
function getProxyAuthHeader(proxyUrl: URL): string | null {
|
|
469
|
-
if (proxyUrl.username) {
|
|
470
|
-
try {
|
|
471
|
-
const user = decodeURIComponent(proxyUrl.username);
|
|
472
|
-
const pass = decodeURIComponent(proxyUrl.password);
|
|
473
|
-
const token = Buffer.from(`${user}:${pass}`).toString("base64");
|
|
474
|
-
return `Basic ${token}`;
|
|
475
|
-
} catch {
|
|
476
|
-
return null;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Decodes chunked transfer-encoding.
|
|
484
|
-
* Observer implements minimal but spec-compliant decoder for proxy HTTPS path.
|
|
485
|
-
*/
|
|
486
|
-
function decodeChunked(body: string): string {
|
|
487
|
-
let out = "";
|
|
488
|
-
let i = 0;
|
|
489
|
-
const len = body.length;
|
|
490
|
-
while (i < len) {
|
|
491
|
-
const crlf = body.indexOf("\r\n", i);
|
|
492
|
-
if (crlf === -1) break;
|
|
493
|
-
const line = body.slice(i, crlf).trim();
|
|
494
|
-
if (!line) {
|
|
495
|
-
i = crlf + 2;
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
// chunk size may have extension ; — ignore
|
|
499
|
-
const semi = line.indexOf(";");
|
|
500
|
-
const sizeHex = semi === -1 ? line : line.slice(0, semi);
|
|
501
|
-
const size = parseInt(sizeHex, 16);
|
|
502
|
-
if (Number.isNaN(size)) break;
|
|
503
|
-
if (size === 0) break; // last chunk
|
|
504
|
-
i = crlf + 2;
|
|
505
|
-
if (i + size > len) {
|
|
506
|
-
// incomplete — take what we have
|
|
507
|
-
out += body.slice(i);
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
out += body.slice(i, i + size);
|
|
511
|
-
i += size;
|
|
512
|
-
// skip trailing CRLF after chunk
|
|
513
|
-
if (body.slice(i, i + 2) === "\r\n") i += 2;
|
|
514
|
-
}
|
|
515
|
-
return out;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// ---------------------------------------------------------------------------
|
|
519
|
-
// Low-level proxy fetch implementations — only node:net/tls/http/https
|
|
520
|
-
// ---------------------------------------------------------------------------
|
|
521
|
-
|
|
522
|
-
interface RawProxyResult {
|
|
523
|
-
status: number;
|
|
524
|
-
statusText: string;
|
|
525
|
-
headers: Record<string, string>;
|
|
526
|
-
bodyText: string;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
interface ProxyFetchResponse {
|
|
530
|
-
ok: boolean;
|
|
531
|
-
status: number;
|
|
532
|
-
statusText: string;
|
|
533
|
-
headers: { get: (name: string) => string | null; [k: string]: any };
|
|
534
|
-
text: () => Promise<string>;
|
|
535
|
-
json: () => Promise<any>;
|
|
536
|
-
arrayBuffer?: () => Promise<ArrayBuffer>;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Creates a fetch-like Response wrapper from raw proxy result.
|
|
541
|
-
*/
|
|
542
|
-
function wrapRawResult(raw: RawProxyResult): ProxyFetchResponse {
|
|
543
|
-
const lowerHeaders: Record<string, string> = {};
|
|
544
|
-
for (const [k, v] of Object.entries(raw.headers)) lowerHeaders[k.toLowerCase()] = v;
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
ok: raw.status >= 200 && raw.status < 300,
|
|
548
|
-
status: raw.status,
|
|
549
|
-
statusText: raw.statusText,
|
|
550
|
-
headers: {
|
|
551
|
-
get: (name: string) => {
|
|
552
|
-
const v = lowerHeaders[name.toLowerCase()];
|
|
553
|
-
return v ?? null;
|
|
554
|
-
},
|
|
555
|
-
...raw.headers,
|
|
556
|
-
},
|
|
557
|
-
text: async () => raw.bodyText,
|
|
558
|
-
json: async () => {
|
|
559
|
-
try {
|
|
560
|
-
return JSON.parse(raw.bodyText);
|
|
561
|
-
} catch (e: any) {
|
|
562
|
-
throw new SearchError(
|
|
563
|
-
`proxy response not JSON: ${e?.message ?? String(e)} — body: ${raw.bodyText.slice(0, 500)}`,
|
|
564
|
-
{
|
|
565
|
-
status: raw.status,
|
|
566
|
-
},
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
},
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* HTTP target via HTTP proxy — absolute URL in request line.
|
|
575
|
-
* Observer uses node:http.request which already handles chunked.
|
|
576
|
-
*
|
|
577
|
-
* @param targetUrl - full target url
|
|
578
|
-
* @param proxyUrl - proxy url object
|
|
579
|
-
* @param init - fetch init
|
|
580
|
-
* @param timeoutMs - timeout
|
|
581
|
-
* @param signal - abort signal
|
|
582
|
-
*/
|
|
583
|
-
function httpRequestViaHttpProxy(
|
|
584
|
-
targetUrl: URL,
|
|
585
|
-
proxyUrl: URL,
|
|
586
|
-
init: RequestInit,
|
|
587
|
-
timeoutMs: number,
|
|
588
|
-
signal?: AbortSignal,
|
|
589
|
-
): Promise<RawProxyResult> {
|
|
590
|
-
return new Promise<RawProxyResult>((resolve, reject) => {
|
|
591
|
-
const method = init.method ?? "POST";
|
|
592
|
-
const bodyStr = typeof init.body === "string" ? init.body : init.body ? (init.body as any).toString() : undefined;
|
|
593
|
-
|
|
594
|
-
const proxyAuth = getProxyAuthHeader(proxyUrl);
|
|
595
|
-
const headers: Record<string, string> = { ...(init.headers as Record<string, string> | undefined) };
|
|
596
|
-
|
|
597
|
-
// Ensure proxy-required headers
|
|
598
|
-
if (!headers["Host"]) headers["Host"] = targetUrl.host;
|
|
599
|
-
if (proxyAuth) headers["Proxy-Authorization"] = proxyAuth;
|
|
600
|
-
if (!headers["Connection"]) headers["Connection"] = "close";
|
|
601
|
-
|
|
602
|
-
const reqOpts: http.RequestOptions = {
|
|
603
|
-
host: proxyUrl.hostname,
|
|
604
|
-
port: proxyUrl.port ? parseInt(proxyUrl.port, 10) : 80,
|
|
605
|
-
method,
|
|
606
|
-
path: targetUrl.toString(), // absolute URI for HTTP proxy
|
|
607
|
-
headers,
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
let finished = false;
|
|
611
|
-
const cleanup = () => {
|
|
612
|
-
finished = true;
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
const req = http.request(reqOpts, (res) => {
|
|
616
|
-
const chunks: Buffer[] = [];
|
|
617
|
-
res.on("data", (c: Buffer) => chunks.push(c));
|
|
618
|
-
res.on("end", () => {
|
|
619
|
-
if (finished) return;
|
|
620
|
-
cleanup();
|
|
621
|
-
const bodyBuf = Buffer.concat(chunks);
|
|
622
|
-
const bodyText = bodyBuf.toString("utf8");
|
|
623
|
-
const hdrs: Record<string, string> = {};
|
|
624
|
-
for (const [k, v] of Object.entries(res.headers)) {
|
|
625
|
-
if (Array.isArray(v)) hdrs[k.toLowerCase()] = v.join(", ");
|
|
626
|
-
else if (v) hdrs[k.toLowerCase()] = String(v);
|
|
627
|
-
}
|
|
628
|
-
resolve({
|
|
629
|
-
status: res.statusCode ?? 0,
|
|
630
|
-
statusText: res.statusMessage ?? "",
|
|
631
|
-
headers: hdrs,
|
|
632
|
-
bodyText,
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
req.on("error", (err) => {
|
|
638
|
-
if (finished) return;
|
|
639
|
-
cleanup();
|
|
640
|
-
reject(err);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
if (timeoutMs) {
|
|
644
|
-
req.setTimeout(timeoutMs, () => {
|
|
645
|
-
req.destroy(new Error(`proxy http request timeout after ${timeoutMs}ms to ${targetUrl.hostname}`));
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
if (signal) {
|
|
650
|
-
if (signal.aborted) {
|
|
651
|
-
req.destroy(new Error("aborted"));
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
const onAbort = () => {
|
|
655
|
-
req.destroy(new Error("aborted by signal"));
|
|
656
|
-
};
|
|
657
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
658
|
-
req.on("close", () => signal.removeEventListener("abort", onAbort));
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
if (bodyStr) req.write(bodyStr);
|
|
662
|
-
req.end();
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* HTTPS target via HTTP proxy using CONNECT tunneling + TLS.
|
|
668
|
-
* Only node:net + node:tls builtins — observer builds tunnel manually.
|
|
669
|
-
*
|
|
670
|
-
* Flow:
|
|
671
|
-
* 1. TCP connect to proxy
|
|
672
|
-
* 2. Send CONNECT host:443
|
|
673
|
-
* 3. Wait for 200
|
|
674
|
-
* 4. TLS over socket
|
|
675
|
-
* 5. Send HTTPS request manually
|
|
676
|
-
*
|
|
677
|
-
* @param targetUrl - https url
|
|
678
|
-
* @param proxyUrl - http proxy url
|
|
679
|
-
* @param init - fetch init
|
|
680
|
-
* @param timeoutMs - timeout
|
|
681
|
-
* @param signal - abort signal
|
|
682
|
-
*/
|
|
683
|
-
function httpsRequestViaHttpProxyConnect(
|
|
684
|
-
targetUrl: URL,
|
|
685
|
-
proxyUrl: URL,
|
|
686
|
-
init: RequestInit,
|
|
687
|
-
timeoutMs: number,
|
|
688
|
-
signal?: AbortSignal,
|
|
689
|
-
): Promise<RawProxyResult> {
|
|
690
|
-
return new Promise<RawProxyResult>((resolve, reject) => {
|
|
691
|
-
const targetHost = targetUrl.hostname;
|
|
692
|
-
const targetPort = targetUrl.port ? parseInt(targetUrl.port, 10) : 443;
|
|
693
|
-
const proxyHost = proxyUrl.hostname;
|
|
694
|
-
const proxyPort = proxyUrl.port ? parseInt(proxyUrl.port, 10) : 8080;
|
|
695
|
-
|
|
696
|
-
let settled = false;
|
|
697
|
-
const doReject = (err: any) => {
|
|
698
|
-
if (settled) return;
|
|
699
|
-
settled = true;
|
|
700
|
-
reject(err);
|
|
701
|
-
};
|
|
702
|
-
const doResolve = (v: RawProxyResult) => {
|
|
703
|
-
if (settled) return;
|
|
704
|
-
settled = true;
|
|
705
|
-
resolve(v);
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
const socket = net.createConnection({ host: proxyHost, port: proxyPort });
|
|
709
|
-
|
|
710
|
-
if (signal?.aborted) {
|
|
711
|
-
socket.destroy(new Error("aborted before connect"));
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const abortHandler = () => {
|
|
716
|
-
socket.destroy(new Error("aborted by signal"));
|
|
717
|
-
};
|
|
718
|
-
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
719
|
-
|
|
720
|
-
let timeoutId: NodeJS.Timeout | null = null;
|
|
721
|
-
if (timeoutMs) {
|
|
722
|
-
timeoutId = setTimeout(() => {
|
|
723
|
-
socket.destroy(new Error(`proxy CONNECT timeout ${timeoutMs}ms`));
|
|
724
|
-
}, timeoutMs);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
socket.on("error", (err) => {
|
|
728
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
729
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
730
|
-
doReject(err);
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
socket.on("connect", () => {
|
|
734
|
-
const proxyAuth = getProxyAuthHeader(proxyUrl);
|
|
735
|
-
const authLine = proxyAuth ? `Proxy-Authorization: ${proxyAuth}\r\n` : "";
|
|
736
|
-
const connectReq =
|
|
737
|
-
`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\n` +
|
|
738
|
-
`Host: ${targetHost}:${targetPort}\r\n` +
|
|
739
|
-
`${authLine}` +
|
|
740
|
-
`Proxy-Connection: Keep-Alive\r\n` +
|
|
741
|
-
`Connection: Keep-Alive\r\n\r\n`;
|
|
742
|
-
socket.write(connectReq);
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
let connectBuf = "";
|
|
746
|
-
const onConnectData = (chunk: Buffer) => {
|
|
747
|
-
connectBuf += chunk.toString("utf8");
|
|
748
|
-
if (!connectBuf.includes("\r\n\r\n")) return; // wait full header
|
|
749
|
-
|
|
750
|
-
socket.off("data", onConnectData);
|
|
751
|
-
|
|
752
|
-
const headerEnd = connectBuf.indexOf("\r\n\r\n");
|
|
753
|
-
const headerPart = connectBuf.slice(0, headerEnd);
|
|
754
|
-
const lines = headerPart.split("\r\n");
|
|
755
|
-
const statusLine = lines[0] ?? "";
|
|
756
|
-
const m = statusLine.match(/HTTP\/\d\.\d\s+(\d+)/);
|
|
757
|
-
const status = m ? parseInt(m[1]!, 10) : 0;
|
|
758
|
-
|
|
759
|
-
if (status !== 200) {
|
|
760
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
761
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
762
|
-
socket.destroy();
|
|
763
|
-
doReject(new SearchError(`proxy CONNECT failed: ${statusLine}`, { status, retryable: status >= 500 }));
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Tunnel established — leftover after \r\n\r\n may be start of TLS handshake? Actually not, it should be empty.
|
|
768
|
-
// Now TLS over socket
|
|
769
|
-
const tlsSocket = tls.connect({
|
|
770
|
-
socket: socket as any,
|
|
771
|
-
servername: targetHost,
|
|
772
|
-
ALPNProtocols: ["http/1.1"],
|
|
773
|
-
} as any);
|
|
774
|
-
|
|
775
|
-
tlsSocket.on("error", (err) => {
|
|
776
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
777
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
778
|
-
doReject(err);
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
if (timeoutMs) {
|
|
782
|
-
tlsSocket.setTimeout(timeoutMs, () => {
|
|
783
|
-
tlsSocket.destroy(new Error(`TLS request timeout ${timeoutMs}ms`));
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
tlsSocket.on("secureConnect", () => {
|
|
788
|
-
const method = init.method ?? "POST";
|
|
789
|
-
const path = `${targetUrl.pathname}${targetUrl.search}`;
|
|
790
|
-
const initHeaders = (init.headers as Record<string, string>) ?? {};
|
|
791
|
-
const headers: Record<string, string> = { ...initHeaders };
|
|
792
|
-
|
|
793
|
-
if (!headers["Host"]) headers["Host"] = targetUrl.host;
|
|
794
|
-
if (!headers["Connection"]) headers["Connection"] = "close";
|
|
795
|
-
|
|
796
|
-
const bodyStr =
|
|
797
|
-
typeof init.body === "string"
|
|
798
|
-
? init.body
|
|
799
|
-
: init.body
|
|
800
|
-
? Buffer.isBuffer(init.body)
|
|
801
|
-
? (init.body as Buffer).toString("utf8")
|
|
802
|
-
: String(init.body)
|
|
803
|
-
: "";
|
|
804
|
-
|
|
805
|
-
if (bodyStr && !headers["Content-Length"]) {
|
|
806
|
-
headers["Content-Length"] = Buffer.byteLength(bodyStr).toString();
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
let reqStr = `${method} ${path} HTTP/1.1\r\n`;
|
|
810
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
811
|
-
reqStr += `${k}: ${v}\r\n`;
|
|
812
|
-
}
|
|
813
|
-
reqStr += `\r\n`;
|
|
814
|
-
if (bodyStr) reqStr += bodyStr;
|
|
815
|
-
|
|
816
|
-
tlsSocket.write(reqStr);
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
let respBuf = Buffer.alloc(0);
|
|
820
|
-
tlsSocket.on("data", (d: Buffer) => {
|
|
821
|
-
respBuf = Buffer.concat([respBuf, d]);
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
tlsSocket.on("end", () => {
|
|
825
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
826
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
827
|
-
try {
|
|
828
|
-
const respStr = respBuf.toString("utf8");
|
|
829
|
-
const hdrEnd = respStr.indexOf("\r\n\r\n");
|
|
830
|
-
if (hdrEnd === -1) {
|
|
831
|
-
doReject(new SearchError(`invalid HTTPS response via proxy tunnel from ${targetHost}`));
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
const hdrPart = respStr.slice(0, hdrEnd);
|
|
835
|
-
let bodyPart = respStr.slice(hdrEnd + 4);
|
|
836
|
-
const hdrLines = hdrPart.split("\r\n");
|
|
837
|
-
const statusLine = hdrLines[0] ?? "";
|
|
838
|
-
const sm = statusLine.match(/HTTP\/\d\.\d\s+(\d+)\s*(.*)/);
|
|
839
|
-
const statusCode = sm ? parseInt(sm[1]!, 10) : 0;
|
|
840
|
-
const statusText = sm ? (sm[2] ?? "") : "";
|
|
841
|
-
|
|
842
|
-
const hdrs: Record<string, string> = {};
|
|
843
|
-
for (let i = 1; i < hdrLines.length; i++) {
|
|
844
|
-
const line = hdrLines[i]!;
|
|
845
|
-
const idx = line.indexOf(":");
|
|
846
|
-
if (idx === -1) continue;
|
|
847
|
-
const k = line.slice(0, idx).trim().toLowerCase();
|
|
848
|
-
const v = line.slice(idx + 1).trim();
|
|
849
|
-
hdrs[k] = v;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// Handle chunked
|
|
853
|
-
if (hdrs["transfer-encoding"]?.toLowerCase() === "chunked") {
|
|
854
|
-
bodyPart = decodeChunked(bodyPart);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
doResolve({
|
|
858
|
-
status: statusCode,
|
|
859
|
-
statusText,
|
|
860
|
-
headers: hdrs,
|
|
861
|
-
bodyText: bodyPart,
|
|
862
|
-
});
|
|
863
|
-
} catch (e: any) {
|
|
864
|
-
doReject(new SearchError(`failed parsing HTTPS proxy response: ${e?.message ?? String(e)}`, { cause: e }));
|
|
865
|
-
} finally {
|
|
866
|
-
try {
|
|
867
|
-
tlsSocket.destroy();
|
|
868
|
-
} catch {
|
|
869
|
-
/* the guarded best-effort operation falls through: the outer flow owns the failure */
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
};
|
|
874
|
-
|
|
875
|
-
socket.on("data", onConnectData);
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* Generic proxy-aware fetch — observer tries native fetch if no proxy,
|
|
881
|
-
* otherwise manual CONNECT for https or absolute-URL for http.
|
|
882
|
-
*
|
|
883
|
-
* @param url - target
|
|
884
|
-
* @param init - fetch init
|
|
885
|
-
* @param proxyUrlStr - resolved proxy url string or null
|
|
886
|
-
* @param timeoutMs - timeout
|
|
887
|
-
*/
|
|
888
|
-
async function fetchWithProxy(
|
|
889
|
-
url: string,
|
|
890
|
-
init: RequestInit,
|
|
891
|
-
proxyUrlStr: string | null,
|
|
892
|
-
timeoutMs: number,
|
|
893
|
-
): Promise<{
|
|
894
|
-
ok: boolean;
|
|
895
|
-
status: number;
|
|
896
|
-
statusText?: string;
|
|
897
|
-
headers: any;
|
|
898
|
-
text: () => Promise<string>;
|
|
899
|
-
json: () => Promise<any>;
|
|
900
|
-
}> {
|
|
901
|
-
if (!proxyUrlStr) {
|
|
902
|
-
// native path — with timeout + signal merge
|
|
903
|
-
const controller = new AbortController();
|
|
904
|
-
const timer = setTimeout(() => controller.abort(new Error(`timeout ${timeoutMs}ms ${url}`)), timeoutMs);
|
|
905
|
-
|
|
906
|
-
// merge external signal if present
|
|
907
|
-
const externalSignal = init.signal as AbortSignal | undefined;
|
|
908
|
-
let onExternalAbort: (() => void) | null = null;
|
|
909
|
-
if (externalSignal) {
|
|
910
|
-
if (externalSignal.aborted) {
|
|
911
|
-
clearTimeout(timer);
|
|
912
|
-
controller.abort(externalSignal.reason);
|
|
913
|
-
} else {
|
|
914
|
-
onExternalAbort = () => controller.abort(externalSignal.reason);
|
|
915
|
-
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
try {
|
|
920
|
-
const res = await fetch(url, { ...init, signal: controller.signal });
|
|
921
|
-
return res as any;
|
|
922
|
-
} finally {
|
|
923
|
-
clearTimeout(timer);
|
|
924
|
-
if (onExternalAbort && externalSignal) {
|
|
925
|
-
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// proxy path
|
|
931
|
-
let proxyUrl: URL;
|
|
932
|
-
let targetUrl: URL;
|
|
933
|
-
try {
|
|
934
|
-
proxyUrl = new URL(proxyUrlStr);
|
|
935
|
-
} catch {
|
|
936
|
-
throw new SearchError(`invalid proxyUrl: ${proxyUrlStr}`);
|
|
937
|
-
}
|
|
938
|
-
try {
|
|
939
|
-
targetUrl = new URL(url);
|
|
940
|
-
} catch {
|
|
941
|
-
throw new SearchError(`invalid target url: ${url}`);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
const method = init.method ?? "POST";
|
|
945
|
-
|
|
946
|
-
const proxyResult =
|
|
947
|
-
targetUrl.protocol === "https:"
|
|
948
|
-
? await httpsRequestViaHttpProxyConnect(
|
|
949
|
-
targetUrl,
|
|
950
|
-
proxyUrl,
|
|
951
|
-
{ ...init, method },
|
|
952
|
-
timeoutMs,
|
|
953
|
-
init.signal as AbortSignal | undefined,
|
|
954
|
-
)
|
|
955
|
-
: await httpRequestViaHttpProxy(
|
|
956
|
-
targetUrl,
|
|
957
|
-
proxyUrl,
|
|
958
|
-
{ ...init, method },
|
|
959
|
-
timeoutMs,
|
|
960
|
-
init.signal as AbortSignal | undefined,
|
|
961
|
-
);
|
|
962
|
-
|
|
963
|
-
return wrapRawResult(proxyResult) as any;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// ---------------------------------------------------------------------------
|
|
967
|
-
// Fetch with timeout helper — mirrors project.ts fetchWithTimeout
|
|
968
|
-
// ---------------------------------------------------------------------------
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* Fetch with timeout using AbortController.
|
|
972
|
-
* Observer pattern: never leaks abort timer.
|
|
973
|
-
*
|
|
974
|
-
* @param url - url
|
|
975
|
-
* @param init - init
|
|
976
|
-
* @param timeoutMs - timeout
|
|
977
|
-
* @param proxyUrl - optional proxy
|
|
978
|
-
*/
|
|
979
|
-
// local variant: diverges from models (models.fetchWithTimeout takes `init.timeoutMs` and aborts a plain fetch; this variant takes an explicit timeoutMs plus proxyUrl and routes through fetchWithProxy)
|
|
980
|
-
export async function fetchWithProxyTimeout(
|
|
981
|
-
url: string,
|
|
982
|
-
init: RequestInit,
|
|
983
|
-
timeoutMs: number = FETCH_TIMEOUT_MS,
|
|
984
|
-
proxyUrl: string | null = null,
|
|
985
|
-
): Promise<any> {
|
|
986
|
-
return fetchWithProxy(url, init, proxyUrl, timeoutMs);
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// ---------------------------------------------------------------------------
|
|
990
|
-
// Thinking budget resolver — per RESEARCH.md 100+ terms
|
|
991
|
-
// ---------------------------------------------------------------------------
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Resolves thinking budget from level or direct number.
|
|
995
|
-
* Observer clamps to Gemini max 32768.
|
|
996
|
-
*
|
|
997
|
-
* @param level - thinkingLevel minimal/low/medium/high
|
|
998
|
-
* @param directBudget - optional direct thinkingBudget
|
|
999
|
-
* @returns clamped budget number
|
|
1000
|
-
*/
|
|
1001
|
-
export function resolveThinkingBudget(level?: ThinkingLevel, directBudget?: number): number {
|
|
1002
|
-
if (typeof directBudget === "number" && Number.isFinite(directBudget)) {
|
|
1003
|
-
const clamped = Math.max(0, Math.min(directBudget, THINKING_BUDGET_MAX_GEMINI));
|
|
1004
|
-
return Math.floor(clamped);
|
|
1005
|
-
}
|
|
1006
|
-
if (level && (THINKING_LEVELS as readonly string[]).includes(level)) {
|
|
1007
|
-
return THINKING_BUDGET_MAP[level] ?? THINKING_BUDGET_MAP.medium;
|
|
1008
|
-
}
|
|
1009
|
-
// default medium per governance
|
|
1010
|
-
return THINKING_BUDGET_MAP.medium;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// ---------------------------------------------------------------------------
|
|
1014
|
-
// Model helpers — 2026 awareness
|
|
1015
|
-
// ---------------------------------------------------------------------------
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Normalizes model name — strips models/ prefix for body, keeps original for logging.
|
|
1019
|
-
* Observer notes cloudcode-pa accepts both gemini-3-flash and models/gemini-3-flash.
|
|
1020
|
-
*
|
|
1021
|
-
* @param raw - model id
|
|
1022
|
-
*/
|
|
1023
|
-
export function normalizeModelName(raw: string): { bare: string; withPrefix: string; original: string } {
|
|
1024
|
-
const original = String(raw ?? "").trim();
|
|
1025
|
-
const bare = original.replace(/^models\//i, "").trim();
|
|
1026
|
-
const withPrefix = `models/${bare}`;
|
|
1027
|
-
return { bare, withPrefix, original };
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/**
|
|
1031
|
-
* Builds request body for cloudcode-pa v1internal:generateContent.
|
|
1032
|
-
* Observer has seen two shapes in the wild; prefers wrapped shape when projectId present.
|
|
1033
|
-
*
|
|
1034
|
-
* Shape A (Antigravity internal with project):
|
|
1035
|
-
* { model, project, request: { contents, tools, generationConfig, systemInstruction } }
|
|
1036
|
-
*
|
|
1037
|
-
* Shape B (flat Gemini SA):
|
|
1038
|
-
* { model, contents, tools, generationConfig, systemInstruction }
|
|
1039
|
-
*
|
|
1040
|
-
* @param model - bare model
|
|
1041
|
-
* @param projectId - optional project
|
|
1042
|
-
* @param contents - Gemini contents array
|
|
1043
|
-
* @param tools - googleSearch + urlContext tools
|
|
1044
|
-
* @param generationConfig - thinking + temp
|
|
1045
|
-
* @param systemInstruction - optional system instruction
|
|
1046
|
-
*/
|
|
1047
|
-
function buildGenerateContentBody(opts: {
|
|
1048
|
-
model: string;
|
|
1049
|
-
projectId?: string | undefined;
|
|
1050
|
-
contents: any[];
|
|
1051
|
-
tools: any[];
|
|
1052
|
-
generationConfig: any;
|
|
1053
|
-
systemInstruction?: any | undefined;
|
|
1054
|
-
}): any {
|
|
1055
|
-
const { model, projectId, contents, tools, generationConfig, systemInstruction } = opts;
|
|
1056
|
-
|
|
1057
|
-
if (projectId) {
|
|
1058
|
-
// wrapped — observed in AM content traffic
|
|
1059
|
-
return {
|
|
1060
|
-
model,
|
|
1061
|
-
project: projectId,
|
|
1062
|
-
request: {
|
|
1063
|
-
contents,
|
|
1064
|
-
tools,
|
|
1065
|
-
generationConfig,
|
|
1066
|
-
...(systemInstruction ? { systemInstruction } : {}),
|
|
1067
|
-
},
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
// flat
|
|
1071
|
-
return {
|
|
1072
|
-
model,
|
|
1073
|
-
contents,
|
|
1074
|
-
tools,
|
|
1075
|
-
generationConfig,
|
|
1076
|
-
...(systemInstruction ? { systemInstruction } : {}),
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/**
|
|
1081
|
-
* Builds alternative body with models/ prefix if primary fails with 404/400.
|
|
1082
|
-
* Fallback chain: bare → withPrefix → wrapped with other model variant.
|
|
1083
|
-
*/
|
|
1084
|
-
function buildAlternativeBodies(
|
|
1085
|
-
primaryBody: any,
|
|
1086
|
-
normalized: ReturnType<typeof normalizeModelName>,
|
|
1087
|
-
projectId?: string,
|
|
1088
|
-
): any[] {
|
|
1089
|
-
const bodies: any[] = [];
|
|
1090
|
-
const { bare, withPrefix } = normalized;
|
|
1091
|
-
|
|
1092
|
-
// 1. try bare flat if primary was wrapped
|
|
1093
|
-
if (primaryBody.project) {
|
|
1094
|
-
bodies.push({
|
|
1095
|
-
model: bare,
|
|
1096
|
-
contents: primaryBody.request.contents,
|
|
1097
|
-
tools: primaryBody.request.tools,
|
|
1098
|
-
generationConfig: primaryBody.request.generationConfig,
|
|
1099
|
-
...(primaryBody.request.systemInstruction ? { systemInstruction: primaryBody.request.systemInstruction } : {}),
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
// 2. try withPrefix
|
|
1103
|
-
bodies.push({
|
|
1104
|
-
...primaryBody,
|
|
1105
|
-
model: withPrefix,
|
|
1106
|
-
});
|
|
1107
|
-
if (primaryBody.request) {
|
|
1108
|
-
bodies.push({
|
|
1109
|
-
model: withPrefix,
|
|
1110
|
-
project: projectId,
|
|
1111
|
-
request: { ...primaryBody.request },
|
|
1112
|
-
});
|
|
1113
|
-
bodies.push({
|
|
1114
|
-
model: withPrefix,
|
|
1115
|
-
contents: primaryBody.request.contents,
|
|
1116
|
-
tools: primaryBody.request.tools,
|
|
1117
|
-
generationConfig: primaryBody.request.generationConfig,
|
|
1118
|
-
...(primaryBody.request.systemInstruction ? { systemInstruction: primaryBody.request.systemInstruction } : {}),
|
|
1119
|
-
});
|
|
1120
|
-
} else {
|
|
1121
|
-
bodies.push({
|
|
1122
|
-
model: withPrefix,
|
|
1123
|
-
contents: primaryBody.contents,
|
|
1124
|
-
tools: primaryBody.tools,
|
|
1125
|
-
generationConfig: primaryBody.generationConfig,
|
|
1126
|
-
...(primaryBody.systemInstruction ? { systemInstruction: primaryBody.systemInstruction } : {}),
|
|
1127
|
-
});
|
|
1128
|
-
}
|
|
1129
|
-
// 3. try fallback models from SEARCH_MODEL_FALLBACKS
|
|
1130
|
-
for (const fallbackModel of SEARCH_MODEL_FALLBACKS) {
|
|
1131
|
-
if (fallbackModel === bare || fallbackModel === withPrefix) continue;
|
|
1132
|
-
bodies.push({
|
|
1133
|
-
model: fallbackModel,
|
|
1134
|
-
project: projectId,
|
|
1135
|
-
request: primaryBody.request ?? {
|
|
1136
|
-
contents: primaryBody.contents,
|
|
1137
|
-
tools: primaryBody.tools,
|
|
1138
|
-
generationConfig: primaryBody.generationConfig,
|
|
1139
|
-
},
|
|
1140
|
-
});
|
|
1141
|
-
if (bodies.length > 8) break;
|
|
1142
|
-
}
|
|
1143
|
-
return bodies;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// ---------------------------------------------------------------------------
|
|
1147
|
-
// Grounding parse — rigorous, handles heterogenous server shapes
|
|
1148
|
-
// ---------------------------------------------------------------------------
|
|
1149
|
-
|
|
1150
|
-
/**
|
|
1151
|
-
* Extracts text from Gemini candidate content parts.
|
|
1152
|
-
* Observer notes parts may be [{text}, {thought}, {inlineData}] — only text counted.
|
|
1153
|
-
*
|
|
1154
|
-
* @param candidate - candidate object
|
|
1155
|
-
*/
|
|
1156
|
-
function extractTextFromCandidate(candidate: any): string {
|
|
1157
|
-
if (!candidate) return "";
|
|
1158
|
-
// direct text field (some internal shapes)
|
|
1159
|
-
if (typeof candidate.text === "string") return candidate.text;
|
|
1160
|
-
if (candidate.content?.parts && Array.isArray(candidate.content.parts)) {
|
|
1161
|
-
const texts: string[] = [];
|
|
1162
|
-
for (const part of candidate.content.parts) {
|
|
1163
|
-
if (part && typeof part.text === "string" && part.text.length > 0) {
|
|
1164
|
-
// thought parts are flagged think? but per spec includeThoughts=false we get only answer
|
|
1165
|
-
if (part.thought === true) continue;
|
|
1166
|
-
texts.push(part.text);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
if (texts.length > 0) return texts.join("\n");
|
|
1170
|
-
}
|
|
1171
|
-
// alternative: candidates[0].content.parts aggregation handled outside
|
|
1172
|
-
return "";
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
/**
|
|
1176
|
-
* Parses groundingMetadata from various response shapes.
|
|
1177
|
-
* Observer is resilient: checks candidate.groundingMetadata, response.groundingMetadata,
|
|
1178
|
-
* and alternative field grounding_metadata.
|
|
1179
|
-
*
|
|
1180
|
-
* @param response - full JSON response
|
|
1181
|
-
* @param candidate - first candidate
|
|
1182
|
-
*/
|
|
1183
|
-
function parseGroundingMetadataFromResponse(response: any, candidate: any): GroundingMetadata | undefined {
|
|
1184
|
-
if (candidate?.groundingMetadata) return candidate.groundingMetadata as GroundingMetadata;
|
|
1185
|
-
if (candidate?.grounding_metadata) return candidate.grounding_metadata as GroundingMetadata;
|
|
1186
|
-
if (response?.groundingMetadata) return response.groundingMetadata as GroundingMetadata;
|
|
1187
|
-
if (response?.grounding_metadata) return response.grounding_metadata as GroundingMetadata;
|
|
1188
|
-
// sometimes in candidates[0].citationMetadata.grounding? not
|
|
1189
|
-
if (response?.candidates && Array.isArray(response.candidates) && response.candidates[0]?.groundingMetadata) {
|
|
1190
|
-
return response.candidates[0].groundingMetadata as GroundingMetadata;
|
|
1191
|
-
}
|
|
1192
|
-
return undefined;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Normalizes groundingChunks into WebGroundingChunk[] with uri/title/domain.
|
|
1197
|
-
* Observer filters out chunks without uri, dedupes by uri preservation order.
|
|
1198
|
-
*
|
|
1199
|
-
* @param meta - groundingMetadata
|
|
1200
|
-
*/
|
|
1201
|
-
function normalizeGroundingChunks(meta: GroundingMetadata | undefined): WebGroundingChunk[] {
|
|
1202
|
-
if (!meta?.groundingChunks || !Array.isArray(meta.groundingChunks)) return [];
|
|
1203
|
-
const seen = new Set<string>();
|
|
1204
|
-
const out: WebGroundingChunk[] = [];
|
|
1205
|
-
for (const ch of meta.groundingChunks) {
|
|
1206
|
-
const web = (ch as any).web ?? (ch as any).retrievedContext ?? {};
|
|
1207
|
-
const rawUri = web.uri ?? (ch as any).uri ?? "";
|
|
1208
|
-
const rawTitle = web.title ?? (ch as any).title ?? "";
|
|
1209
|
-
if (!rawUri || typeof rawUri !== "string") continue;
|
|
1210
|
-
// normalize uri
|
|
1211
|
-
let uri: string;
|
|
1212
|
-
try {
|
|
1213
|
-
// basic validation — must be http(s)
|
|
1214
|
-
const u = new URL(rawUri);
|
|
1215
|
-
if (!u.protocol.startsWith("http")) continue;
|
|
1216
|
-
uri = u.toString();
|
|
1217
|
-
} catch {
|
|
1218
|
-
continue;
|
|
1219
|
-
}
|
|
1220
|
-
if (seen.has(uri)) continue;
|
|
1221
|
-
seen.add(uri);
|
|
1222
|
-
let domain: string | undefined;
|
|
1223
|
-
try {
|
|
1224
|
-
domain = new URL(uri).hostname;
|
|
1225
|
-
} catch {
|
|
1226
|
-
domain = undefined;
|
|
1227
|
-
}
|
|
1228
|
-
out.push({
|
|
1229
|
-
uri,
|
|
1230
|
-
title: String(rawTitle || domain || uri).slice(0, 500),
|
|
1231
|
-
domain,
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
return out;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
/**
|
|
1238
|
-
* Extracts webSearchQueries from groundingMetadata.
|
|
1239
|
-
*/
|
|
1240
|
-
function extractWebSearchQueries(meta: GroundingMetadata | undefined): string[] {
|
|
1241
|
-
if (!meta) return [];
|
|
1242
|
-
const queries = meta.webSearchQueries ?? meta.retrievalQueries ?? [];
|
|
1243
|
-
if (!Array.isArray(queries)) return [];
|
|
1244
|
-
return queries
|
|
1245
|
-
.filter((q) => typeof q === "string" && (q as string).trim().length > 0)
|
|
1246
|
-
.map((q) => String(q).trim())
|
|
1247
|
-
.slice(0, 20); // cap 20 queries per spec sanity
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// ---------------------------------------------------------------------------
|
|
1251
|
-
// Core — executeSearch
|
|
1252
|
-
// ---------------------------------------------------------------------------
|
|
1253
|
-
|
|
1254
|
-
/**
|
|
1255
|
-
* Executes grounded search using Gemini native googleSearch + urlContext tools.
|
|
1256
|
-
* Third-person observer pattern: imitates Antigravity's google_search tool wrapper
|
|
1257
|
-
* but calls cloudcode-pa.googleapis.com directly, no localhost proxy.
|
|
1258
|
-
*
|
|
1259
|
-
* The observer:
|
|
1260
|
-
* 1. Validates query + accessToken.
|
|
1261
|
-
* 2. Resolves proxy (env + option) with NO_PROXY bypass.
|
|
1262
|
-
* 3. Resolves thinking budget from level or direct number, clamped 32k.
|
|
1263
|
-
* 4. Builds contents + tools [{googleSearch:{}},{urlContext:{}}] + generationConfig.
|
|
1264
|
-
* 5. Builds Antigravity headers (Bearer, UA dynamic, Client-Metadata, Api-Client)
|
|
1265
|
-
* stripping x-goog-user-project per bypass spec.
|
|
1266
|
-
* 6. Cascades endpoints PROD → DAILY → SANDBOX on 403/404/5xx with jitter 0-80ms.
|
|
1267
|
-
* 7. Parses groundingMetadata.groundingChunks[].web.uri/title and webSearchQueries.
|
|
1268
|
-
*
|
|
1269
|
-
* @param input - query + optional systemPrompt
|
|
1270
|
-
* @param auth - OAuth access token + projectId + UA override
|
|
1271
|
-
* @param opts - model, thinking, proxy, timeout, endpoints, etc.
|
|
1272
|
-
* @returns grounded SearchResult with parsed chunks
|
|
1273
|
-
*
|
|
1274
|
-
* @throws SearchError on validation, timeout, 401, exhausted cascade, or invalid JSON.
|
|
1275
|
-
*
|
|
1276
|
-
* @example
|
|
1277
|
-
* ```ts
|
|
1278
|
-
* const result = await executeSearch(
|
|
1279
|
-
* { query: "latest Gemini 3 flash grounding changes 2026" },
|
|
1280
|
-
* { accessToken: "...", projectId: "rising-fact-p41fc" },
|
|
1281
|
-
* { thinkingLevel: "medium", useUrlContext: true }
|
|
1282
|
-
* );
|
|
1283
|
-
* console.log(result.text);
|
|
1284
|
-
* console.log(result.groundingChunks.map(c=>c.uri));
|
|
1285
|
-
* ```
|
|
1286
|
-
*/
|
|
1287
|
-
export async function executeSearch(
|
|
1288
|
-
input: ExecuteSearchInput,
|
|
1289
|
-
auth: ExecuteSearchAuth,
|
|
1290
|
-
opts: ExecuteSearchOptions = {},
|
|
1291
|
-
): Promise<SearchResult> {
|
|
1292
|
-
// -----------------------------------------------------------------------
|
|
1293
|
-
// Validation — production ready, no placeholder
|
|
1294
|
-
// -----------------------------------------------------------------------
|
|
1295
|
-
if (!input || typeof input.query !== "string" || input.query.trim().length === 0) {
|
|
1296
|
-
throw new SearchError("query must be a non-empty string", { code: "INVALID_QUERY" });
|
|
1297
|
-
}
|
|
1298
|
-
const query = input.query.trim();
|
|
1299
|
-
if (query.length > 20000) {
|
|
1300
|
-
throw new SearchError(`query too long (${query.length} > 20000 chars)`, { code: "QUERY_TOO_LONG" });
|
|
1301
|
-
}
|
|
1302
|
-
if (!auth || typeof auth.accessToken !== "string" || auth.accessToken.trim().length === 0) {
|
|
1303
|
-
throw new SearchError("accessToken required — missing auth.accessToken Bearer", { code: "MISSING_TOKEN" });
|
|
1304
|
-
}
|
|
1305
|
-
const accessToken = auth.accessToken.trim();
|
|
1306
|
-
// basic Bearer format check — JWT-ish or ya29.
|
|
1307
|
-
if (accessToken.length < 20) {
|
|
1308
|
-
throw new SearchError("accessToken appears too short to be valid", { code: "INVALID_TOKEN" });
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
// -----------------------------------------------------------------------
|
|
1312
|
-
// Resolve model — SEARCH_MODEL gemini-3-flash per spec
|
|
1313
|
-
// -----------------------------------------------------------------------
|
|
1314
|
-
const rawModel = (opts.model ?? SEARCH_MODEL).trim() || SEARCH_MODEL;
|
|
1315
|
-
const normalizedModel = normalizeModelName(rawModel);
|
|
1316
|
-
const modelBare = normalizedModel.bare || SEARCH_MODEL;
|
|
1317
|
-
|
|
1318
|
-
// -----------------------------------------------------------------------
|
|
1319
|
-
// Thinking budget — level → budget map, clamped
|
|
1320
|
-
// -----------------------------------------------------------------------
|
|
1321
|
-
const thinkingBudget = resolveThinkingBudget(opts.thinkingLevel, opts.thinkingBudget);
|
|
1322
|
-
|
|
1323
|
-
// -----------------------------------------------------------------------
|
|
1324
|
-
// Tools — native googleSearch + urlContext per requirement
|
|
1325
|
-
// -----------------------------------------------------------------------
|
|
1326
|
-
const useGoogleSearch = opts.useGoogleSearch !== false; // default true
|
|
1327
|
-
const useUrlContext = opts.useUrlContext !== false; // default true
|
|
1328
|
-
const tools: any[] = [];
|
|
1329
|
-
if (useGoogleSearch) tools.push({ googleSearch: {} });
|
|
1330
|
-
if (useUrlContext) tools.push({ urlContext: {} });
|
|
1331
|
-
// fallback — at least one grounding tool required
|
|
1332
|
-
if (tools.length === 0) tools.push({ googleSearch: {} });
|
|
1333
|
-
|
|
1334
|
-
// -----------------------------------------------------------------------
|
|
1335
|
-
// Contents + system instruction
|
|
1336
|
-
// -----------------------------------------------------------------------
|
|
1337
|
-
const contents: any[] = [
|
|
1338
|
-
{
|
|
1339
|
-
role: "user",
|
|
1340
|
-
parts: [
|
|
1341
|
-
{
|
|
1342
|
-
text: input.additionalContext ? `${query}\n\nContext: ${input.additionalContext}` : query,
|
|
1343
|
-
},
|
|
1344
|
-
],
|
|
1345
|
-
},
|
|
1346
|
-
];
|
|
1347
|
-
|
|
1348
|
-
const systemInstruction = input.systemPrompt
|
|
1349
|
-
? { parts: [{ text: String(input.systemPrompt).slice(0, 8000) }] }
|
|
1350
|
-
: undefined;
|
|
1351
|
-
|
|
1352
|
-
const generationConfig: any = {
|
|
1353
|
-
// Thinking — Gemini 3 supports thinkingConfig
|
|
1354
|
-
thinkingConfig: {
|
|
1355
|
-
thinkingBudget,
|
|
1356
|
-
includeThoughts: false,
|
|
1357
|
-
},
|
|
1358
|
-
temperature: typeof opts.temperature === "number" ? opts.temperature : 0.2,
|
|
1359
|
-
maxOutputTokens: typeof opts.maxOutputTokens === "number" ? opts.maxOutputTokens : 8192,
|
|
1360
|
-
};
|
|
1361
|
-
|
|
1362
|
-
const primaryBody = buildGenerateContentBody({
|
|
1363
|
-
model: modelBare,
|
|
1364
|
-
projectId: auth.projectId,
|
|
1365
|
-
contents,
|
|
1366
|
-
tools,
|
|
1367
|
-
generationConfig,
|
|
1368
|
-
systemInstruction,
|
|
1369
|
-
});
|
|
1370
|
-
|
|
1371
|
-
// -----------------------------------------------------------------------
|
|
1372
|
-
// Proxy resolution — supports HTTP_PROXY/HTTPS_PROXY/NO_PROXY
|
|
1373
|
-
// -----------------------------------------------------------------------
|
|
1374
|
-
// Use first endpoint as NO_PROXY check target — if endpoint in NO_PROXY list, bypass
|
|
1375
|
-
const firstEndpointForProxyCheck = (opts.endpoints?.[0] ?? DEFAULT_ENDPOINT_ORDER[0]) + GENERATE_CONTENT_ENDPOINT;
|
|
1376
|
-
const proxyResolution = resolveProxyUrl(opts.proxyUrl, firstEndpointForProxyCheck);
|
|
1377
|
-
const proxyUrlStr = proxyResolution.proxyUrl;
|
|
1378
|
-
|
|
1379
|
-
// -----------------------------------------------------------------------
|
|
1380
|
-
// UA + headers — observer builds dynamic UA antigravity/{ver} {os}/{arch}
|
|
1381
|
-
// -----------------------------------------------------------------------
|
|
1382
|
-
const version = auth.version ?? ANTIGRAVITY_VERSION_FALLBACK;
|
|
1383
|
-
const userAgent = auth.userAgent ?? getDynamicUserAgent(version);
|
|
1384
|
-
const extraHeaders: Record<string, string> = {};
|
|
1385
|
-
// allow projectId as header? No — strip per bypass. Project sent in body only.
|
|
1386
|
-
|
|
1387
|
-
const baseHeaders = buildAntagravityHeaders(accessToken, { userAgent, extra: extraHeaders });
|
|
1388
|
-
|
|
1389
|
-
// -----------------------------------------------------------------------
|
|
1390
|
-
// Endpoint cascade
|
|
1391
|
-
// -----------------------------------------------------------------------
|
|
1392
|
-
const endpoints = opts.endpoints && opts.endpoints.length > 0 ? opts.endpoints : [...DEFAULT_ENDPOINT_ORDER];
|
|
1393
|
-
const timeoutMs = typeof opts.timeoutMs === "number" && opts.timeoutMs > 0 ? opts.timeoutMs : SEARCH_TIMEOUT_MS;
|
|
1394
|
-
const jitterEnabled = opts.jitter !== false;
|
|
1395
|
-
|
|
1396
|
-
let lastError: SearchError | Error | null = null;
|
|
1397
|
-
let attemptedBodiesTried = 0;
|
|
1398
|
-
|
|
1399
|
-
// For debugging
|
|
1400
|
-
if (opts.debug) {
|
|
1401
|
-
// eslint-disable-next-line no-console
|
|
1402
|
-
console.debug(
|
|
1403
|
-
`[search] observer start queryLen=${query.length} model=${modelBare} budget=${thinkingBudget} proxy=${proxyResolution.source} tokenHash=${hashToken(accessToken)} endpoints=${endpoints.join(",")}`,
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Try each endpoint, with body fallback strategy
|
|
1408
|
-
for (let epIdx = 0; epIdx < endpoints.length; epIdx++) {
|
|
1409
|
-
const base = endpoints[epIdx]!.replace(/\/$/, "");
|
|
1410
|
-
const url = `${base}${GENERATE_CONTENT_ENDPOINT}`;
|
|
1411
|
-
|
|
1412
|
-
// Try primary body + alternative bodies if 400/404
|
|
1413
|
-
const bodiesToTry = [primaryBody, ...buildAlternativeBodies(primaryBody, normalizedModel, auth.projectId)];
|
|
1414
|
-
// deduplicate by JSON stringify quickly via model name (small optimization)
|
|
1415
|
-
const seenModels = new Set<string>();
|
|
1416
|
-
const dedupedBodies: any[] = [];
|
|
1417
|
-
for (const b of bodiesToTry) {
|
|
1418
|
-
const key = `${b.model}|${b.project ?? ""}|${b.request ? "wrapped" : "flat"}`;
|
|
1419
|
-
if (seenModels.has(key)) continue;
|
|
1420
|
-
seenModels.add(key);
|
|
1421
|
-
dedupedBodies.push(b);
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
for (let bodyIdx = 0; bodyIdx < dedupedBodies.length; bodyIdx++) {
|
|
1425
|
-
const body = dedupedBodies[bodyIdx];
|
|
1426
|
-
attemptedBodiesTried++;
|
|
1427
|
-
|
|
1428
|
-
// To avoid infinite loops, limit total attempts
|
|
1429
|
-
if (opts.maxRetries && attemptedBodiesTried > (opts.maxRetries ?? 8) * endpoints.length) break;
|
|
1430
|
-
|
|
1431
|
-
const fetchHeaders = { ...baseHeaders };
|
|
1432
|
-
|
|
1433
|
-
const fetchInit: RequestInit = {
|
|
1434
|
-
method: "POST",
|
|
1435
|
-
headers: fetchHeaders as any,
|
|
1436
|
-
body: JSON.stringify(body),
|
|
1437
|
-
};
|
|
1438
|
-
|
|
1439
|
-
if (opts.debug) {
|
|
1440
|
-
// eslint-disable-next-line no-console
|
|
1441
|
-
console.debug(
|
|
1442
|
-
`[search] attempt ep=${base} bodyModel=${body.model} wrapped=${!!body.request} attempt=${attemptedBodiesTried}`,
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
let res: any;
|
|
1447
|
-
try {
|
|
1448
|
-
res = await fetchWithProxyTimeout(url, fetchInit, timeoutMs, proxyUrlStr);
|
|
1449
|
-
} catch (networkErr: any) {
|
|
1450
|
-
// network failure — retryable
|
|
1451
|
-
const err = new SearchError(`network failure to ${base}: ${networkErr?.message ?? String(networkErr)}`, {
|
|
1452
|
-
endpoint: base,
|
|
1453
|
-
retryable: true,
|
|
1454
|
-
code: "NETWORK_ERROR",
|
|
1455
|
-
cause: networkErr,
|
|
1456
|
-
});
|
|
1457
|
-
lastError = err;
|
|
1458
|
-
if (opts.debug) console.debug(`[search] network error ${err.message}`);
|
|
1459
|
-
// jitter before next endpoint/body
|
|
1460
|
-
if (jitterEnabled) await sleep(getJitter());
|
|
1461
|
-
continue; // next body / endpoint
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// HTTP status handling
|
|
1465
|
-
const status: number = res.status ?? 0;
|
|
1466
|
-
if (!res.ok) {
|
|
1467
|
-
let bodyText = "";
|
|
1468
|
-
try {
|
|
1469
|
-
bodyText = await res.text();
|
|
1470
|
-
} catch {
|
|
1471
|
-
bodyText = "";
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
// Parse error for log trimming
|
|
1475
|
-
const truncated = bodyText.slice(0, 2000);
|
|
1476
|
-
|
|
1477
|
-
const retryable = isRetryableStatus(status);
|
|
1478
|
-
const code =
|
|
1479
|
-
status === 401
|
|
1480
|
-
? "UNAUTHORIZED"
|
|
1481
|
-
: status === 403
|
|
1482
|
-
? "FORBIDDEN"
|
|
1483
|
-
: status === 429
|
|
1484
|
-
? "RATE_LIMITED"
|
|
1485
|
-
: status >= 500
|
|
1486
|
-
? "SERVER_ERROR"
|
|
1487
|
-
: "HTTP_ERROR";
|
|
1488
|
-
|
|
1489
|
-
const err = new SearchError(`search failed ${status} at ${base}: ${truncated}`, {
|
|
1490
|
-
status,
|
|
1491
|
-
endpoint: base,
|
|
1492
|
-
retryable,
|
|
1493
|
-
code,
|
|
1494
|
-
});
|
|
1495
|
-
|
|
1496
|
-
lastError = err;
|
|
1497
|
-
|
|
1498
|
-
if (opts.debug) {
|
|
1499
|
-
// eslint-disable-next-line no-console
|
|
1500
|
-
console.debug(`[search] http ${status} retryable=${retryable} body=${truncated.slice(0, 500)}`);
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
// 401 Unauthorized — no point retrying other endpoints with same token
|
|
1504
|
-
if (status === 401) {
|
|
1505
|
-
throw err;
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
// 403/404/429/5xx — try next endpoint (cascade) or next body variant if 400
|
|
1509
|
-
if (status === 400 && bodyIdx === 0) {
|
|
1510
|
-
// bad request maybe due to model naming — try next body variant without switching endpoint
|
|
1511
|
-
if (jitterEnabled) await sleep(getJitter());
|
|
1512
|
-
continue;
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
if (retryable) {
|
|
1516
|
-
if (jitterEnabled) await sleep(getJitter());
|
|
1517
|
-
// break inner body loop to try next endpoint
|
|
1518
|
-
break;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// non-retryable 400 etc. — if first body, try alternative bodies
|
|
1522
|
-
if (status === 400 && bodyIdx < dedupedBodies.length - 1) {
|
|
1523
|
-
if (jitterEnabled) await sleep(getJitter());
|
|
1524
|
-
continue;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
// otherwise keep as lastError and try next endpoint
|
|
1528
|
-
if (jitterEnabled) await sleep(getJitter());
|
|
1529
|
-
break;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// -----------------------------------------------------------------
|
|
1533
|
-
// Success path — parse JSON
|
|
1534
|
-
// -----------------------------------------------------------------
|
|
1535
|
-
let json: any;
|
|
1536
|
-
try {
|
|
1537
|
-
json = await res.json();
|
|
1538
|
-
} catch (parseErr: any) {
|
|
1539
|
-
let txt = "";
|
|
1540
|
-
try {
|
|
1541
|
-
txt = await res.text();
|
|
1542
|
-
} catch {
|
|
1543
|
-
/* the guarded best-effort operation falls through: the outer flow owns the failure */
|
|
1544
|
-
}
|
|
1545
|
-
throw new SearchError(
|
|
1546
|
-
`failed parsing search JSON from ${base}: ${parseErr?.message ?? String(parseErr)} — text: ${txt.slice(0, 1000)}`,
|
|
1547
|
-
{
|
|
1548
|
-
status,
|
|
1549
|
-
endpoint: base,
|
|
1550
|
-
code: "INVALID_JSON",
|
|
1551
|
-
cause: parseErr,
|
|
1552
|
-
},
|
|
1553
|
-
);
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// Response shape may be array? Some internal endpoints wrap in []?
|
|
1557
|
-
// Normalize: if json is array, take first
|
|
1558
|
-
const responseObj = Array.isArray(json) ? json[0] : json;
|
|
1559
|
-
|
|
1560
|
-
// Candidates extraction — heterogenous shapes
|
|
1561
|
-
const candidates = responseObj?.candidates ?? responseObj?.response?.candidates ?? [];
|
|
1562
|
-
const firstCandidate = Array.isArray(candidates) && candidates.length > 0 ? candidates[0] : responseObj;
|
|
1563
|
-
|
|
1564
|
-
const text =
|
|
1565
|
-
extractTextFromCandidate(firstCandidate) ||
|
|
1566
|
-
(typeof responseObj?.text === "string" ? responseObj.text : "") ||
|
|
1567
|
-
(typeof responseObj?.output === "string" ? responseObj.output : "");
|
|
1568
|
-
|
|
1569
|
-
if (!text || text.trim().length === 0) {
|
|
1570
|
-
// Sometimes grounding only returns metadata without text when query is just URLs? Treat as empty but still return grounding
|
|
1571
|
-
if (opts.debug)
|
|
1572
|
-
console.debug(`[search] empty text but status ok, json keys=${Object.keys(responseObj).join(",")}`);
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
const groundingMeta = parseGroundingMetadataFromResponse(responseObj, firstCandidate);
|
|
1576
|
-
const groundingChunks = normalizeGroundingChunks(groundingMeta);
|
|
1577
|
-
const webSearchQueries = extractWebSearchQueries(groundingMeta);
|
|
1578
|
-
|
|
1579
|
-
const result: SearchResult = {
|
|
1580
|
-
text: text ?? "",
|
|
1581
|
-
query,
|
|
1582
|
-
model: body.model ?? modelBare,
|
|
1583
|
-
grounded: groundingChunks.length > 0 || webSearchQueries.length > 0,
|
|
1584
|
-
webSearchQueries,
|
|
1585
|
-
groundingChunks,
|
|
1586
|
-
groundingSupports: groundingMeta?.groundingSupports,
|
|
1587
|
-
searchEntryPoint: groundingMeta?.searchEntryPoint,
|
|
1588
|
-
groundingMetadata: opts.includeGrounding !== false ? groundingMeta : undefined,
|
|
1589
|
-
raw: responseObj,
|
|
1590
|
-
endpointUsed: base,
|
|
1591
|
-
thinkingBudgetUsed: thinkingBudget,
|
|
1592
|
-
isFallbackProject: auth.projectId === "rising-fact-p41fc" || body.project === "rising-fact-p41fc",
|
|
1593
|
-
};
|
|
1594
|
-
|
|
1595
|
-
if (opts.debug) {
|
|
1596
|
-
// eslint-disable-next-line no-console
|
|
1597
|
-
console.debug(
|
|
1598
|
-
`[search] success endpoint=${base} grounded=${result.grounded} chunks=${result.groundingChunks.length} queries=${result.webSearchQueries.length} textLen=${result.text.length}`,
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
return result;
|
|
1603
|
-
} // end bodies loop
|
|
1604
|
-
|
|
1605
|
-
// after body loop for this endpoint, if we exhausted bodies and have retryable error, continue to next endpoint
|
|
1606
|
-
// jitter already applied
|
|
1607
|
-
} // end endpoints loop
|
|
1608
|
-
|
|
1609
|
-
// -----------------------------------------------------------------------
|
|
1610
|
-
// All endpoints failed — throw last error with context
|
|
1611
|
-
// -----------------------------------------------------------------------
|
|
1612
|
-
if (lastError) {
|
|
1613
|
-
if (lastError instanceof SearchError) throw lastError;
|
|
1614
|
-
throw new SearchError(
|
|
1615
|
-
`all search endpoints failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`,
|
|
1616
|
-
{
|
|
1617
|
-
code: "ALL_ENDPOINTS_FAILED",
|
|
1618
|
-
cause: lastError,
|
|
1619
|
-
},
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
throw new SearchError(
|
|
1623
|
-
`all search endpoints failed without specific error — attempted ${endpoints.length} endpoints, ${attemptedBodiesTried} bodies`,
|
|
1624
|
-
{
|
|
1625
|
-
code: "ALL_ENDPOINTS_FAILED",
|
|
1626
|
-
},
|
|
1627
|
-
);
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
// ---------------------------------------------------------------------------
|
|
1631
|
-
// Convenience wrappers — library-first root-first exports
|
|
1632
|
-
// ---------------------------------------------------------------------------
|
|
1633
|
-
|
|
1634
|
-
/**
|
|
1635
|
-
* Convenience: execute search with project fallback rising-fact-p41fc.
|
|
1636
|
-
* Observer notes this fallback works for Antigravity but 403 for Gemini CLI models.
|
|
1637
|
-
*
|
|
1638
|
-
* @param query - search query
|
|
1639
|
-
* @param accessToken - bearer token
|
|
1640
|
-
* @param opts - optional search options + projectId override
|
|
1641
|
-
*/
|
|
1642
|
-
export async function searchWithFallback(
|
|
1643
|
-
query: string,
|
|
1644
|
-
accessToken: string,
|
|
1645
|
-
opts: ExecuteSearchOptions & { projectId?: string; systemPrompt?: string } = {},
|
|
1646
|
-
): Promise<SearchResult> {
|
|
1647
|
-
const projectId = opts.projectId ?? "rising-fact-p41fc";
|
|
1648
|
-
return executeSearch({ query, systemPrompt: opts.systemPrompt }, { accessToken, projectId }, opts);
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* Extracts only grounding URIs from result — helper for TUI.
|
|
1653
|
-
*
|
|
1654
|
-
* @param result - SearchResult
|
|
1655
|
-
* @returns uri strings
|
|
1656
|
-
*/
|
|
1657
|
-
export function extractGroundingUris(result: SearchResult): string[] {
|
|
1658
|
-
return result.groundingChunks.map((c) => c.uri).filter(Boolean);
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
/**
|
|
1662
|
-
* Extracts grounding titles for UI display.
|
|
1663
|
-
*/
|
|
1664
|
-
export function extractGroundingTitles(result: SearchResult): Array<{ uri: string; title: string }> {
|
|
1665
|
-
return result.groundingChunks.map((c) => ({ uri: c.uri, title: c.title }));
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// ---------------------------------------------------------------------------
|
|
1669
|
-
// Plugin helpers - search gating, grounding formatting, image generation
|
|
1670
|
-
// (ported from search(3).ts lineage)
|
|
1671
|
-
// ---------------------------------------------------------------------------
|
|
1672
|
-
|
|
1673
|
-
/**
|
|
1674
|
-
* Decides whether native googleSearch grounding should be enabled for a model
|
|
1675
|
-
* given plugin config (`google_search_enabled`: true | "auto" | falsy).
|
|
1676
|
-
*/
|
|
1677
|
-
export const shouldEnableSearch = (model: string, cfg: any): boolean => {
|
|
1678
|
-
if (cfg?.google_search_enabled === true) return true;
|
|
1679
|
-
if (cfg?.google_search_enabled === "auto") {
|
|
1680
|
-
const l = model.toLowerCase();
|
|
1681
|
-
return l.includes("gemini") && !l.includes("image");
|
|
1682
|
-
}
|
|
1683
|
-
return false;
|
|
1684
|
-
};
|
|
1685
|
-
|
|
1686
|
-
/** Builds the native grounding tool list: googleSearch + urlContext. */
|
|
1687
|
-
export const buildSearchTools = (): any[] => [{ googleSearch: {} }, { urlContext: {} }];
|
|
1688
|
-
|
|
1689
|
-
/** Normalizes groundingMetadata chunks into {uri,title} pairs.
|
|
1690
|
-
* Identical to core.parseGrounding — imported from the frozen owner and
|
|
1691
|
-
* re-exported above. */
|
|
1692
|
-
|
|
1693
|
-
/** Formats grounding sources as markdown citation lines. */
|
|
1694
|
-
export const extractCitations = (grounding: any[]): string =>
|
|
1695
|
-
grounding.map((g) => `[${g.title || "source"}](${g.uri})`).join("\n");
|
|
1696
|
-
|
|
1697
|
-
/**
|
|
1698
|
-
* Saves a base64-encoded PNG under ~/.config/opencode/generated-images (or a
|
|
1699
|
-
* custom dir) using an timestamped random filename; returns the written path.
|
|
1700
|
-
*/
|
|
1701
|
-
export const saveGeneratedImage = async (b64: string, dir?: string): Promise<string> => {
|
|
1702
|
-
const base = dir || pathJoin(os.homedir(), ".config", "opencode", "generated-images");
|
|
1703
|
-
try {
|
|
1704
|
-
mkdirSync(base, { recursive: true });
|
|
1705
|
-
} catch {
|
|
1706
|
-
/* the guarded best-effort operation falls through: the outer flow owns the failure */
|
|
1707
|
-
}
|
|
1708
|
-
const name = `img-${Date.now()}-${Math.random().toString(16).slice(2)}.png`;
|
|
1709
|
-
const filePath = pathJoin(base, name);
|
|
1710
|
-
const buf = Buffer.from(b64, "base64");
|
|
1711
|
-
writeFileSync(filePath, buf);
|
|
1712
|
-
return filePath;
|
|
1713
|
-
};
|
|
1714
|
-
|
|
1715
|
-
/** Renders grounding sources as a markdown bullet list. */
|
|
1716
|
-
export const groundingToMarkdown = (grounding: any[]): string =>
|
|
1717
|
-
grounding.map((g) => `- [${g.title || g.uri}](${g.uri})`).join("\n");
|
|
1718
|
-
|
|
1719
|
-
// ---------------------------------------------------------------------------
|
|
1720
|
-
// Barrel default export — library-first
|
|
1721
|
-
// ---------------------------------------------------------------------------
|
|
1722
|
-
|
|
1723
|
-
export const Search = {
|
|
1724
|
-
SEARCH_MODEL,
|
|
1725
|
-
SEARCH_MODEL_PREVIEW,
|
|
1726
|
-
SEARCH_MODEL_FALLBACKS,
|
|
1727
|
-
CODE_ASSIST_ENDPOINTS,
|
|
1728
|
-
DEFAULT_ENDPOINT_ORDER,
|
|
1729
|
-
GENERATE_CONTENT_ENDPOINT,
|
|
1730
|
-
THINKING_BUDGET_MAP,
|
|
1731
|
-
THINKING_LEVELS,
|
|
1732
|
-
THINKING_BUDGET_MAX_GEMINI,
|
|
1733
|
-
ANTIGRAVITY_VERSION_FALLBACK,
|
|
1734
|
-
ANTIGRAVITY_USER_AGENT_FALLBACK,
|
|
1735
|
-
getDynamicUserAgent,
|
|
1736
|
-
buildAntagravityHeaders,
|
|
1737
|
-
fetchWithProxyTimeout,
|
|
1738
|
-
resolveProxyUrl,
|
|
1739
|
-
isNoProxyMatch,
|
|
1740
|
-
resolveThinkingBudget,
|
|
1741
|
-
normalizeModelName,
|
|
1742
|
-
executeSearch,
|
|
1743
|
-
searchWithFallback,
|
|
1744
|
-
extractGroundingUris,
|
|
1745
|
-
extractGroundingTitles,
|
|
1746
|
-
shouldEnableSearch,
|
|
1747
|
-
buildSearchTools,
|
|
1748
|
-
parseGrounding,
|
|
1749
|
-
extractCitations,
|
|
1750
|
-
isImageGenModel,
|
|
1751
|
-
buildImageGenConfig,
|
|
1752
|
-
saveGeneratedImage,
|
|
1753
|
-
groundingToMarkdown,
|
|
1754
|
-
SearchError,
|
|
1755
|
-
isRetryableStatus,
|
|
1756
|
-
getJitter,
|
|
1757
|
-
fnv1a32,
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
|
-
export default Search;
|