@cortexkit/opencode-antigravity-auth 1.1.0 → 2.0.0
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/README.md +225 -569
- package/dist/cli.js +3790 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18767 -13829
- package/dist/index.js.map +4 -4
- package/dist/src/antigravity/oauth.d.ts +1 -1
- package/dist/src/antigravity/oauth.js +1 -1
- package/dist/src/cli.d.ts +21 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +237 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/cache.js +13 -13
- package/dist/src/hooks/auto-update-checker/cache.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/checker.d.ts +1 -1
- package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/checker.js +75 -51
- package/dist/src/hooks/auto-update-checker/checker.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/constants.js +12 -12
- package/dist/src/hooks/auto-update-checker/constants.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/index.d.ts +6 -6
- package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/index.js +17 -17
- package/dist/src/hooks/auto-update-checker/index.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/logging.js +2 -2
- package/dist/src/hooks/auto-update-checker/logging.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -1
- package/dist/src/plugin/account-access.d.ts +99 -0
- package/dist/src/plugin/account-access.d.ts.map +1 -0
- package/dist/src/plugin/account-access.js +480 -0
- package/dist/src/plugin/account-access.js.map +1 -0
- package/dist/src/plugin/accounts.d.ts +7 -256
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +31 -1367
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/agy-request-metadata.d.ts +1 -1
- package/dist/src/plugin/agy-request-metadata.js +1 -1
- package/dist/src/plugin/agy-transport.d.ts +1 -1
- package/dist/src/plugin/agy-transport.js +1 -1
- package/dist/src/plugin/auth-doctor.d.ts +6 -6
- package/dist/src/plugin/auth-doctor.d.ts.map +1 -1
- package/dist/src/plugin/auth-doctor.js +74 -66
- package/dist/src/plugin/auth-doctor.js.map +1 -1
- package/dist/src/plugin/auth-drift.d.ts +4 -4
- package/dist/src/plugin/auth-drift.d.ts.map +1 -1
- package/dist/src/plugin/auth-drift.js +15 -15
- package/dist/src/plugin/auth-drift.js.map +1 -1
- package/dist/src/plugin/auth-loader.d.ts +35 -0
- package/dist/src/plugin/auth-loader.d.ts.map +1 -0
- package/dist/src/plugin/auth-loader.js +144 -0
- package/dist/src/plugin/auth-loader.js.map +1 -0
- package/dist/src/plugin/auth.d.ts +1 -1
- package/dist/src/plugin/auth.js +1 -1
- package/dist/src/plugin/cache/index.d.ts +1 -1
- package/dist/src/plugin/cache/index.d.ts.map +1 -1
- package/dist/src/plugin/cache/index.js +1 -1
- package/dist/src/plugin/cache/index.js.map +1 -1
- package/dist/src/plugin/cache/signature-cache.d.ts +1 -1
- package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -1
- package/dist/src/plugin/cache/signature-cache.js +23 -24
- package/dist/src/plugin/cache/signature-cache.js.map +1 -1
- package/dist/src/plugin/cache.d.ts +6 -5
- package/dist/src/plugin/cache.d.ts.map +1 -1
- package/dist/src/plugin/cache.js +21 -7
- package/dist/src/plugin/cache.js.map +1 -1
- package/dist/src/plugin/catalog.d.ts +23 -0
- package/dist/src/plugin/catalog.d.ts.map +1 -0
- package/dist/src/plugin/catalog.js +60 -0
- package/dist/src/plugin/catalog.js.map +1 -0
- package/dist/src/plugin/cli.d.ts +7 -7
- package/dist/src/plugin/cli.d.ts.map +1 -1
- package/dist/src/plugin/cli.js +62 -55
- package/dist/src/plugin/cli.js.map +1 -1
- package/dist/src/plugin/command-data.d.ts +276 -0
- package/dist/src/plugin/command-data.d.ts.map +1 -0
- package/dist/src/plugin/command-data.js +325 -0
- package/dist/src/plugin/command-data.js.map +1 -0
- package/dist/src/plugin/commands.d.ts +151 -0
- package/dist/src/plugin/commands.d.ts.map +1 -0
- package/dist/src/plugin/commands.js +639 -0
- package/dist/src/plugin/commands.js.map +1 -0
- package/dist/src/plugin/config/index.d.ts +2 -2
- package/dist/src/plugin/config/index.d.ts.map +1 -1
- package/dist/src/plugin/config/index.js +2 -2
- package/dist/src/plugin/config/index.js.map +1 -1
- package/dist/src/plugin/config/loader.d.ts +1 -1
- package/dist/src/plugin/config/loader.d.ts.map +1 -1
- package/dist/src/plugin/config/loader.js +18 -16
- package/dist/src/plugin/config/loader.js.map +1 -1
- package/dist/src/plugin/config/models.d.ts +2 -2
- package/dist/src/plugin/config/models.d.ts.map +1 -1
- package/dist/src/plugin/config/models.js +1 -1
- package/dist/src/plugin/config/models.js.map +1 -1
- package/dist/src/plugin/config/operator-settings-schema.d.ts +29 -0
- package/dist/src/plugin/config/operator-settings-schema.d.ts.map +1 -0
- package/dist/src/plugin/config/operator-settings-schema.js +28 -0
- package/dist/src/plugin/config/operator-settings-schema.js.map +1 -0
- package/dist/src/plugin/config/schema.d.ts +21 -2
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +85 -25
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/config/updater.d.ts.map +1 -1
- package/dist/src/plugin/config/updater.js +15 -15
- package/dist/src/plugin/config/updater.js.map +1 -1
- package/dist/src/plugin/config/writer.d.ts +34 -0
- package/dist/src/plugin/config/writer.d.ts.map +1 -0
- package/dist/src/plugin/config/writer.js +81 -0
- package/dist/src/plugin/config/writer.js.map +1 -0
- package/dist/src/plugin/core/streaming/index.d.ts +1 -1
- package/dist/src/plugin/core/streaming/index.d.ts.map +1 -1
- package/dist/src/plugin/core/streaming/index.js +1 -1
- package/dist/src/plugin/core/streaming/index.js.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.js +85 -44
- package/dist/src/plugin/core/streaming/transformer.js.map +1 -1
- package/dist/src/plugin/core/streaming/types.d.ts.map +1 -1
- package/dist/src/plugin/debug.d.ts +3 -3
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +85 -47
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/dependencies.d.ts +129 -0
- package/dist/src/plugin/dependencies.d.ts.map +1 -0
- package/dist/src/plugin/dependencies.js +107 -0
- package/dist/src/plugin/dependencies.js.map +1 -0
- package/dist/src/plugin/errors.d.ts +27 -0
- package/dist/src/plugin/errors.d.ts.map +1 -1
- package/dist/src/plugin/errors.js +29 -3
- package/dist/src/plugin/errors.js.map +1 -1
- package/dist/src/plugin/event-handler.d.ts +53 -0
- package/dist/src/plugin/event-handler.d.ts.map +1 -0
- package/dist/src/plugin/event-handler.js +101 -0
- package/dist/src/plugin/event-handler.js.map +1 -0
- package/dist/src/plugin/fetch/retry-state.d.ts +61 -0
- package/dist/src/plugin/fetch/retry-state.d.ts.map +1 -0
- package/dist/src/plugin/fetch/retry-state.js +248 -0
- package/dist/src/plugin/fetch/retry-state.js.map +1 -0
- package/dist/src/plugin/fetch/warmup.d.ts +30 -0
- package/dist/src/plugin/fetch/warmup.d.ts.map +1 -0
- package/dist/src/plugin/fetch/warmup.js +76 -0
- package/dist/src/plugin/fetch/warmup.js.map +1 -0
- package/dist/src/plugin/fetch-interceptor.d.ts +59 -0
- package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -0
- package/dist/src/plugin/fetch-interceptor.js +1387 -0
- package/dist/src/plugin/fetch-interceptor.js.map +1 -0
- package/dist/src/plugin/fetch-routing.d.ts +26 -0
- package/dist/src/plugin/fetch-routing.d.ts.map +1 -0
- package/dist/src/plugin/fetch-routing.js +99 -0
- package/dist/src/plugin/fetch-routing.js.map +1 -0
- package/dist/src/plugin/fingerprint.d.ts +1 -1
- package/dist/src/plugin/fingerprint.js +1 -1
- package/dist/src/plugin/gemini-dump.d.ts +5 -5
- package/dist/src/plugin/gemini-dump.d.ts.map +1 -1
- package/dist/src/plugin/gemini-dump.js +109 -55
- package/dist/src/plugin/gemini-dump.js.map +1 -1
- package/dist/src/plugin/google-search-tool.d.ts +9 -0
- package/dist/src/plugin/google-search-tool.d.ts.map +1 -0
- package/dist/src/plugin/google-search-tool.js +56 -0
- package/dist/src/plugin/google-search-tool.js.map +1 -0
- package/dist/src/plugin/image-saver.d.ts.map +1 -1
- package/dist/src/plugin/image-saver.js +16 -16
- package/dist/src/plugin/image-saver.js.map +1 -1
- package/dist/src/plugin/index.d.ts +23 -0
- package/dist/src/plugin/index.d.ts.map +1 -0
- package/dist/src/plugin/index.js +308 -0
- package/dist/src/plugin/index.js.map +1 -0
- package/dist/src/plugin/killswitch.d.ts +94 -0
- package/dist/src/plugin/killswitch.d.ts.map +1 -0
- package/dist/src/plugin/killswitch.js +194 -0
- package/dist/src/plugin/killswitch.js.map +1 -0
- package/dist/src/plugin/lifecycle.d.ts +40 -0
- package/dist/src/plugin/lifecycle.d.ts.map +1 -0
- package/dist/src/plugin/lifecycle.js +73 -0
- package/dist/src/plugin/lifecycle.js.map +1 -0
- package/dist/src/plugin/logger.d.ts +10 -1
- package/dist/src/plugin/logger.d.ts.map +1 -1
- package/dist/src/plugin/logger.js +38 -12
- package/dist/src/plugin/logger.js.map +1 -1
- package/dist/src/plugin/logging-utils.d.ts +35 -1
- package/dist/src/plugin/logging-utils.d.ts.map +1 -1
- package/dist/src/plugin/logging-utils.js +98 -15
- package/dist/src/plugin/logging-utils.js.map +1 -1
- package/dist/src/plugin/model-registry.d.ts +1 -1
- package/dist/src/plugin/model-registry.js +1 -1
- package/dist/src/plugin/oauth-login.d.ts +22 -0
- package/dist/src/plugin/oauth-login.d.ts.map +1 -0
- package/dist/src/plugin/oauth-login.js +37 -0
- package/dist/src/plugin/oauth-login.js.map +1 -0
- package/dist/src/plugin/oauth-methods.d.ts +42 -0
- package/dist/src/plugin/oauth-methods.d.ts.map +1 -0
- package/dist/src/plugin/oauth-methods.js +1284 -0
- package/dist/src/plugin/oauth-methods.js.map +1 -0
- package/dist/src/plugin/operator-settings.d.ts +40 -0
- package/dist/src/plugin/operator-settings.d.ts.map +1 -0
- package/dist/src/plugin/operator-settings.js +148 -0
- package/dist/src/plugin/operator-settings.js.map +1 -0
- package/dist/src/plugin/persist-account-pool.d.ts +37 -0
- package/dist/src/plugin/persist-account-pool.d.ts.map +1 -0
- package/dist/src/plugin/persist-account-pool.js +142 -0
- package/dist/src/plugin/persist-account-pool.js.map +1 -0
- package/dist/src/plugin/project.d.ts +1 -1
- package/dist/src/plugin/project.js +1 -1
- package/dist/src/plugin/prompt-context.d.ts.map +1 -1
- package/dist/src/plugin/prompt-context.js +11 -11
- package/dist/src/plugin/prompt-context.js.map +1 -1
- package/dist/src/plugin/quota.d.ts +97 -41
- package/dist/src/plugin/quota.d.ts.map +1 -1
- package/dist/src/plugin/quota.js +273 -285
- package/dist/src/plugin/quota.js.map +1 -1
- package/dist/src/plugin/recovery/constants.d.ts.map +1 -1
- package/dist/src/plugin/recovery/constants.js +24 -15
- package/dist/src/plugin/recovery/constants.js.map +1 -1
- package/dist/src/plugin/recovery/index.d.ts +3 -3
- package/dist/src/plugin/recovery/index.d.ts.map +1 -1
- package/dist/src/plugin/recovery/index.js +3 -3
- package/dist/src/plugin/recovery/index.js.map +1 -1
- package/dist/src/plugin/recovery/storage.d.ts +1 -1
- package/dist/src/plugin/recovery/storage.d.ts.map +1 -1
- package/dist/src/plugin/recovery/storage.js +31 -39
- package/dist/src/plugin/recovery/storage.js.map +1 -1
- package/dist/src/plugin/recovery/types.d.ts +12 -12
- package/dist/src/plugin/recovery/types.d.ts.map +1 -1
- package/dist/src/plugin/recovery.d.ts +3 -3
- package/dist/src/plugin/recovery.d.ts.map +1 -1
- package/dist/src/plugin/recovery.js +62 -58
- package/dist/src/plugin/recovery.js.map +1 -1
- package/dist/src/plugin/refresh-queue.d.ts +5 -2
- package/dist/src/plugin/refresh-queue.d.ts.map +1 -1
- package/dist/src/plugin/refresh-queue.js +37 -15
- package/dist/src/plugin/refresh-queue.js.map +1 -1
- package/dist/src/plugin/request-helpers.d.ts +1 -1
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +451 -352
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +7 -7
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +622 -384
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/rotation.d.ts +1 -168
- package/dist/src/plugin/rotation.d.ts.map +1 -1
- package/dist/src/plugin/rotation.js +1 -327
- package/dist/src/plugin/rotation.js.map +1 -1
- package/dist/src/plugin/search.d.ts.map +1 -1
- package/dist/src/plugin/search.js +39 -35
- package/dist/src/plugin/search.js.map +1 -1
- package/dist/src/plugin/server.d.ts +1 -1
- package/dist/src/plugin/server.d.ts.map +1 -1
- package/dist/src/plugin/server.js +42 -37
- package/dist/src/plugin/server.js.map +1 -1
- package/dist/src/plugin/session-context.d.ts +2 -1
- package/dist/src/plugin/session-context.d.ts.map +1 -1
- package/dist/src/plugin/session-context.js +11 -7
- package/dist/src/plugin/session-context.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +59 -120
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +140 -485
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/stores/signature-store.d.ts.map +1 -1
- package/dist/src/plugin/stores/signature-store.js.map +1 -1
- package/dist/src/plugin/thinking-recovery.d.ts.map +1 -1
- package/dist/src/plugin/thinking-recovery.js +35 -33
- package/dist/src/plugin/thinking-recovery.js.map +1 -1
- package/dist/src/plugin/token.d.ts +2 -2
- package/dist/src/plugin/token.d.ts.map +1 -1
- package/dist/src/plugin/token.js +30 -21
- package/dist/src/plugin/token.js.map +1 -1
- package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +1 -1
- package/dist/src/plugin/transform/cross-model-sanitizer.js +1 -1
- package/dist/src/plugin/transform/index.d.ts +1 -1
- package/dist/src/plugin/transform/index.js +1 -1
- package/dist/src/plugin/transform/model-resolver.d.ts +1 -1
- package/dist/src/plugin/transform/model-resolver.js +1 -1
- package/dist/src/plugin/transform/types.d.ts +1 -1
- package/dist/src/plugin/transform/types.js +1 -1
- package/dist/src/plugin/types.d.ts +25 -69
- package/dist/src/plugin/types.d.ts.map +1 -1
- package/dist/src/plugin/ui/ansi.d.ts.map +1 -1
- package/dist/src/plugin/ui/ansi.js.map +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
- package/dist/src/plugin/ui/auth-menu.js +82 -31
- package/dist/src/plugin/ui/auth-menu.js.map +1 -1
- package/dist/src/plugin/ui/confirm.d.ts.map +1 -1
- package/dist/src/plugin/ui/confirm.js.map +1 -1
- package/dist/src/plugin/ui/model-status.d.ts +3 -3
- package/dist/src/plugin/ui/model-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/model-status.js +3 -3
- package/dist/src/plugin/ui/model-status.js.map +1 -1
- package/dist/src/plugin/ui/quota-status.d.ts +4 -4
- package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/quota-status.js +51 -49
- package/dist/src/plugin/ui/quota-status.js.map +1 -1
- package/dist/src/plugin/ui/select.d.ts.map +1 -1
- package/dist/src/plugin/ui/select.js +22 -12
- package/dist/src/plugin/ui/select.js.map +1 -1
- package/dist/src/plugin/version.d.ts +1 -1
- package/dist/src/plugin/version.js +1 -1
- package/dist/src/rpc/notifications.d.ts +6 -0
- package/dist/src/rpc/notifications.d.ts.map +1 -0
- package/dist/src/rpc/notifications.js +46 -0
- package/dist/src/rpc/notifications.js.map +1 -0
- package/dist/src/rpc/port-file.d.ts +13 -0
- package/dist/src/rpc/port-file.d.ts.map +1 -0
- package/dist/src/rpc/port-file.js +109 -0
- package/dist/src/rpc/port-file.js.map +1 -0
- package/dist/src/rpc/protocol.d.ts +22 -0
- package/dist/src/rpc/protocol.d.ts.map +1 -0
- package/dist/src/rpc/protocol.js +1 -0
- package/dist/src/rpc/protocol.js.map +1 -0
- package/dist/src/rpc/rpc-client.d.ts +10 -0
- package/dist/src/rpc/rpc-client.d.ts.map +1 -0
- package/dist/src/rpc/rpc-client.js +58 -0
- package/dist/src/rpc/rpc-client.js.map +1 -0
- package/dist/src/rpc/rpc-dir.d.ts +4 -0
- package/dist/src/rpc/rpc-dir.d.ts.map +1 -0
- package/dist/src/rpc/rpc-dir.js +19 -0
- package/dist/src/rpc/rpc-dir.js.map +1 -0
- package/dist/src/rpc/rpc-server.d.ts +13 -0
- package/dist/src/rpc/rpc-server.d.ts.map +1 -0
- package/dist/src/rpc/rpc-server.js +231 -0
- package/dist/src/rpc/rpc-server.js.map +1 -0
- package/dist/src/sidebar-state.d.ts +240 -0
- package/dist/src/sidebar-state.d.ts.map +1 -0
- package/dist/src/sidebar-state.js +520 -0
- package/dist/src/sidebar-state.js.map +1 -0
- package/dist/src/tui/command-dialogs.d.ts +61 -0
- package/dist/src/tui/command-dialogs.d.ts.map +1 -0
- package/dist/src/tui/command-dialogs.js +497 -0
- package/dist/src/tui/command-dialogs.js.map +1 -0
- package/dist/src/tui/file-logger.d.ts +41 -0
- package/dist/src/tui/file-logger.d.ts.map +1 -0
- package/dist/src/tui/file-logger.js +114 -0
- package/dist/src/tui/file-logger.js.map +1 -0
- package/dist/src/tui-preferences.d.ts +40 -0
- package/dist/src/tui-preferences.d.ts.map +1 -0
- package/dist/src/tui-preferences.js +328 -0
- package/dist/src/tui-preferences.js.map +1 -0
- package/dist/src/tui.d.ts +68 -0
- package/dist/src/tui.d.ts.map +1 -0
- package/dist/src/tui.js +662 -0
- package/dist/src/tui.js.map +1 -0
- package/package.json +46 -22
- package/src/plugin/command-data.ts +651 -0
- package/src/rpc/port-file.ts +143 -0
- package/src/rpc/protocol.ts +31 -0
- package/src/rpc/rpc-client.ts +102 -0
- package/src/rpc/rpc-dir.ts +24 -0
- package/src/sidebar-state.ts +717 -0
- package/src/tui/command-dialogs.tsx +699 -0
- package/src/tui/entry.mjs +74 -0
- package/src/tui/file-logger.ts +145 -0
- package/src/tui-compiled/plugin/command-data.ts +651 -0
- package/src/tui-compiled/rpc/port-file.ts +143 -0
- package/src/tui-compiled/rpc/protocol.ts +31 -0
- package/src/tui-compiled/rpc/rpc-client.ts +102 -0
- package/src/tui-compiled/rpc/rpc-dir.ts +24 -0
- package/src/tui-compiled/sidebar-state.ts +717 -0
- package/src/tui-compiled/tui/command-dialogs.tsx +659 -0
- package/src/tui-compiled/tui/file-logger.ts +145 -0
- package/src/tui-compiled/tui-preferences.ts +416 -0
- package/src/tui-compiled/tui.tsx +1150 -0
- package/src/tui-preferences.ts +416 -0
- package/src/tui.tsx +1117 -0
- package/dist/src/plugin.d.ts +0 -48
- package/dist/src/plugin.d.ts.map +0 -1
- package/dist/src/plugin.js +0 -3311
- package/dist/src/plugin.js.map +0 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const ANTIGRAVITY_PREVIEW_LINK = "https://goo.gle/enable-preview-features"; // TODO: Update to Antigravity link if available
|
|
1
|
+
import { EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION, EMPTY_SCHEMA_PLACEHOLDER_NAME, } from '../constants';
|
|
2
|
+
import { getKeepThinking } from './config';
|
|
3
|
+
import { processImageData } from './image-saver';
|
|
4
|
+
import { createLogger } from './logger';
|
|
5
|
+
const log = createLogger('request-helpers');
|
|
6
|
+
const ANTIGRAVITY_PREVIEW_LINK = 'https://goo.gle/enable-preview-features'; // TODO: Update to Antigravity link if available
|
|
8
7
|
// ============================================================================
|
|
9
8
|
// JSON SCHEMA CLEANING FOR ANTIGRAVITY API
|
|
10
9
|
// Ported from CLIProxyAPI's CleanJSONSchemaForAntigravity (gemini_schema.go)
|
|
@@ -14,26 +13,41 @@ const ANTIGRAVITY_PREVIEW_LINK = "https://goo.gle/enable-preview-features"; // T
|
|
|
14
13
|
* Claude/Gemini reject these in VALIDATED mode.
|
|
15
14
|
*/
|
|
16
15
|
const UNSUPPORTED_CONSTRAINTS = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
'minLength',
|
|
17
|
+
'maxLength',
|
|
18
|
+
'exclusiveMinimum',
|
|
19
|
+
'exclusiveMaximum',
|
|
20
|
+
'pattern',
|
|
21
|
+
'minItems',
|
|
22
|
+
'maxItems',
|
|
23
|
+
'format',
|
|
24
|
+
'default',
|
|
25
|
+
'examples',
|
|
20
26
|
];
|
|
21
27
|
/**
|
|
22
28
|
* Keywords that should be removed after hint extraction.
|
|
23
29
|
*/
|
|
24
30
|
const UNSUPPORTED_KEYWORDS = [
|
|
25
31
|
...UNSUPPORTED_CONSTRAINTS,
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
'$schema',
|
|
33
|
+
'$defs',
|
|
34
|
+
'definitions',
|
|
35
|
+
'const',
|
|
36
|
+
'$ref',
|
|
37
|
+
'additionalProperties',
|
|
38
|
+
'propertyNames',
|
|
39
|
+
'title',
|
|
40
|
+
'$id',
|
|
41
|
+
'$comment',
|
|
28
42
|
];
|
|
29
43
|
/**
|
|
30
44
|
* Appends a hint to a schema's description field.
|
|
31
45
|
*/
|
|
32
46
|
function appendDescriptionHint(schema, hint) {
|
|
33
|
-
if (!schema || typeof schema !==
|
|
47
|
+
if (!schema || typeof schema !== 'object') {
|
|
34
48
|
return schema;
|
|
35
49
|
}
|
|
36
|
-
const existing = typeof schema.description ===
|
|
50
|
+
const existing = typeof schema.description === 'string' ? schema.description : '';
|
|
37
51
|
const newDescription = existing ? `${existing} (${hint})` : hint;
|
|
38
52
|
return { ...schema, description: newDescription };
|
|
39
53
|
}
|
|
@@ -42,20 +56,20 @@ function appendDescriptionHint(schema, hint) {
|
|
|
42
56
|
* $ref: "#/$defs/Foo" → { type: "object", description: "See: Foo" }
|
|
43
57
|
*/
|
|
44
58
|
function convertRefsToHints(schema) {
|
|
45
|
-
if (!schema || typeof schema !==
|
|
59
|
+
if (!schema || typeof schema !== 'object') {
|
|
46
60
|
return schema;
|
|
47
61
|
}
|
|
48
62
|
if (Array.isArray(schema)) {
|
|
49
|
-
return schema.map(item => convertRefsToHints(item));
|
|
63
|
+
return schema.map((item) => convertRefsToHints(item));
|
|
50
64
|
}
|
|
51
65
|
// If this object has $ref, replace it with a hint
|
|
52
|
-
if (typeof schema.$ref ===
|
|
66
|
+
if (typeof schema.$ref === 'string') {
|
|
53
67
|
const refVal = schema.$ref;
|
|
54
|
-
const defName = refVal.includes(
|
|
68
|
+
const defName = refVal.includes('/') ? refVal.split('/').pop() : refVal;
|
|
55
69
|
const hint = `See: ${defName}`;
|
|
56
|
-
const existingDesc = typeof schema.description ===
|
|
70
|
+
const existingDesc = typeof schema.description === 'string' ? schema.description : '';
|
|
57
71
|
const newDescription = existingDesc ? `${existingDesc} (${hint})` : hint;
|
|
58
|
-
return { type:
|
|
72
|
+
return { type: 'object', description: newDescription };
|
|
59
73
|
}
|
|
60
74
|
// Recursively process all properties
|
|
61
75
|
const result = {};
|
|
@@ -69,15 +83,15 @@ function convertRefsToHints(schema) {
|
|
|
69
83
|
* { const: "foo" } → { enum: ["foo"] }
|
|
70
84
|
*/
|
|
71
85
|
function convertConstToEnum(schema) {
|
|
72
|
-
if (!schema || typeof schema !==
|
|
86
|
+
if (!schema || typeof schema !== 'object') {
|
|
73
87
|
return schema;
|
|
74
88
|
}
|
|
75
89
|
if (Array.isArray(schema)) {
|
|
76
|
-
return schema.map(item => convertConstToEnum(item));
|
|
90
|
+
return schema.map((item) => convertConstToEnum(item));
|
|
77
91
|
}
|
|
78
92
|
const result = {};
|
|
79
93
|
for (const [key, value] of Object.entries(schema)) {
|
|
80
|
-
if (key ===
|
|
94
|
+
if (key === 'const' && !schema.enum) {
|
|
81
95
|
result.enum = [value];
|
|
82
96
|
}
|
|
83
97
|
else {
|
|
@@ -91,21 +105,23 @@ function convertConstToEnum(schema) {
|
|
|
91
105
|
* { enum: ["a", "b", "c"] } → adds "(Allowed: a, b, c)" to description
|
|
92
106
|
*/
|
|
93
107
|
function addEnumHints(schema) {
|
|
94
|
-
if (!schema || typeof schema !==
|
|
108
|
+
if (!schema || typeof schema !== 'object') {
|
|
95
109
|
return schema;
|
|
96
110
|
}
|
|
97
111
|
if (Array.isArray(schema)) {
|
|
98
|
-
return schema.map(item => addEnumHints(item));
|
|
112
|
+
return schema.map((item) => addEnumHints(item));
|
|
99
113
|
}
|
|
100
114
|
let result = { ...schema };
|
|
101
115
|
// Add enum hint if enum has 2-10 items
|
|
102
|
-
if (Array.isArray(result.enum) &&
|
|
103
|
-
|
|
116
|
+
if (Array.isArray(result.enum) &&
|
|
117
|
+
result.enum.length > 1 &&
|
|
118
|
+
result.enum.length <= 10) {
|
|
119
|
+
const vals = result.enum.map((v) => String(v)).join(', ');
|
|
104
120
|
result = appendDescriptionHint(result, `Allowed: ${vals}`);
|
|
105
121
|
}
|
|
106
122
|
// Recursively process nested objects
|
|
107
123
|
for (const [key, value] of Object.entries(result)) {
|
|
108
|
-
if (key !==
|
|
124
|
+
if (key !== 'enum' && typeof value === 'object' && value !== null) {
|
|
109
125
|
result[key] = addEnumHints(value);
|
|
110
126
|
}
|
|
111
127
|
}
|
|
@@ -116,19 +132,21 @@ function addEnumHints(schema) {
|
|
|
116
132
|
* { additionalProperties: false } → adds "(No extra properties allowed)" to description
|
|
117
133
|
*/
|
|
118
134
|
function addAdditionalPropertiesHints(schema) {
|
|
119
|
-
if (!schema || typeof schema !==
|
|
135
|
+
if (!schema || typeof schema !== 'object') {
|
|
120
136
|
return schema;
|
|
121
137
|
}
|
|
122
138
|
if (Array.isArray(schema)) {
|
|
123
|
-
return schema.map(item => addAdditionalPropertiesHints(item));
|
|
139
|
+
return schema.map((item) => addAdditionalPropertiesHints(item));
|
|
124
140
|
}
|
|
125
141
|
let result = { ...schema };
|
|
126
142
|
if (result.additionalProperties === false) {
|
|
127
|
-
result = appendDescriptionHint(result,
|
|
143
|
+
result = appendDescriptionHint(result, 'No extra properties allowed');
|
|
128
144
|
}
|
|
129
145
|
// Recursively process nested objects
|
|
130
146
|
for (const [key, value] of Object.entries(result)) {
|
|
131
|
-
if (key !==
|
|
147
|
+
if (key !== 'additionalProperties' &&
|
|
148
|
+
typeof value === 'object' &&
|
|
149
|
+
value !== null) {
|
|
132
150
|
result[key] = addAdditionalPropertiesHints(value);
|
|
133
151
|
}
|
|
134
152
|
}
|
|
@@ -139,22 +157,23 @@ function addAdditionalPropertiesHints(schema) {
|
|
|
139
157
|
* { minLength: 1, maxLength: 100 } → adds "(minLength: 1) (maxLength: 100)" to description
|
|
140
158
|
*/
|
|
141
159
|
function moveConstraintsToDescription(schema) {
|
|
142
|
-
if (!schema || typeof schema !==
|
|
160
|
+
if (!schema || typeof schema !== 'object') {
|
|
143
161
|
return schema;
|
|
144
162
|
}
|
|
145
163
|
if (Array.isArray(schema)) {
|
|
146
|
-
return schema.map(item => moveConstraintsToDescription(item));
|
|
164
|
+
return schema.map((item) => moveConstraintsToDescription(item));
|
|
147
165
|
}
|
|
148
166
|
let result = { ...schema };
|
|
149
167
|
// Move constraint values to description
|
|
150
168
|
for (const constraint of UNSUPPORTED_CONSTRAINTS) {
|
|
151
|
-
if (result[constraint] !== undefined &&
|
|
169
|
+
if (result[constraint] !== undefined &&
|
|
170
|
+
typeof result[constraint] !== 'object') {
|
|
152
171
|
result = appendDescriptionHint(result, `${constraint}: ${result[constraint]}`);
|
|
153
172
|
}
|
|
154
173
|
}
|
|
155
174
|
// Recursively process nested objects
|
|
156
175
|
for (const [key, value] of Object.entries(result)) {
|
|
157
|
-
if (typeof value ===
|
|
176
|
+
if (typeof value === 'object' && value !== null) {
|
|
158
177
|
result[key] = moveConstraintsToDescription(value);
|
|
159
178
|
}
|
|
160
179
|
}
|
|
@@ -166,22 +185,22 @@ function moveConstraintsToDescription(schema) {
|
|
|
166
185
|
* → { properties: { a: ..., b: ... } }
|
|
167
186
|
*/
|
|
168
187
|
function mergeAllOf(schema) {
|
|
169
|
-
if (!schema || typeof schema !==
|
|
188
|
+
if (!schema || typeof schema !== 'object') {
|
|
170
189
|
return schema;
|
|
171
190
|
}
|
|
172
191
|
if (Array.isArray(schema)) {
|
|
173
|
-
return schema.map(item => mergeAllOf(item));
|
|
192
|
+
return schema.map((item) => mergeAllOf(item));
|
|
174
193
|
}
|
|
175
|
-
|
|
194
|
+
const result = { ...schema };
|
|
176
195
|
// If this object has allOf, merge its contents
|
|
177
196
|
if (Array.isArray(result.allOf)) {
|
|
178
197
|
const merged = {};
|
|
179
198
|
const mergedRequired = [];
|
|
180
199
|
for (const item of result.allOf) {
|
|
181
|
-
if (!item || typeof item !==
|
|
200
|
+
if (!item || typeof item !== 'object')
|
|
182
201
|
continue;
|
|
183
202
|
// Merge properties
|
|
184
|
-
if (item.properties && typeof item.properties ===
|
|
203
|
+
if (item.properties && typeof item.properties === 'object') {
|
|
185
204
|
merged.properties = { ...merged.properties, ...item.properties };
|
|
186
205
|
}
|
|
187
206
|
// Merge required arrays
|
|
@@ -194,7 +213,9 @@ function mergeAllOf(schema) {
|
|
|
194
213
|
}
|
|
195
214
|
// Copy other fields from allOf items
|
|
196
215
|
for (const [key, value] of Object.entries(item)) {
|
|
197
|
-
if (key !==
|
|
216
|
+
if (key !== 'properties' &&
|
|
217
|
+
key !== 'required' &&
|
|
218
|
+
merged[key] === undefined) {
|
|
198
219
|
merged[key] = value;
|
|
199
220
|
}
|
|
200
221
|
}
|
|
@@ -204,12 +225,16 @@ function mergeAllOf(schema) {
|
|
|
204
225
|
result.properties = { ...result.properties, ...merged.properties };
|
|
205
226
|
}
|
|
206
227
|
if (mergedRequired.length > 0) {
|
|
207
|
-
const existingRequired = Array.isArray(result.required)
|
|
228
|
+
const existingRequired = Array.isArray(result.required)
|
|
229
|
+
? result.required
|
|
230
|
+
: [];
|
|
208
231
|
result.required = Array.from(new Set([...existingRequired, ...mergedRequired]));
|
|
209
232
|
}
|
|
210
233
|
// Copy other merged fields
|
|
211
234
|
for (const [key, value] of Object.entries(merged)) {
|
|
212
|
-
if (key !==
|
|
235
|
+
if (key !== 'properties' &&
|
|
236
|
+
key !== 'required' &&
|
|
237
|
+
result[key] === undefined) {
|
|
213
238
|
result[key] = value;
|
|
214
239
|
}
|
|
215
240
|
}
|
|
@@ -217,7 +242,7 @@ function mergeAllOf(schema) {
|
|
|
217
242
|
}
|
|
218
243
|
// Recursively process nested objects
|
|
219
244
|
for (const [key, value] of Object.entries(result)) {
|
|
220
|
-
if (typeof value ===
|
|
245
|
+
if (typeof value === 'object' && value !== null) {
|
|
221
246
|
result[key] = mergeAllOf(value);
|
|
222
247
|
}
|
|
223
248
|
}
|
|
@@ -228,24 +253,24 @@ function mergeAllOf(schema) {
|
|
|
228
253
|
* Higher score = more preferred.
|
|
229
254
|
*/
|
|
230
255
|
function scoreSchemaOption(schema) {
|
|
231
|
-
if (!schema || typeof schema !==
|
|
232
|
-
return { score: 0, typeName:
|
|
256
|
+
if (!schema || typeof schema !== 'object') {
|
|
257
|
+
return { score: 0, typeName: 'unknown' };
|
|
233
258
|
}
|
|
234
259
|
const type = schema.type;
|
|
235
260
|
// Object or has properties = highest priority
|
|
236
|
-
if (type ===
|
|
237
|
-
return { score: 3, typeName:
|
|
261
|
+
if (type === 'object' || schema.properties) {
|
|
262
|
+
return { score: 3, typeName: 'object' };
|
|
238
263
|
}
|
|
239
264
|
// Array or has items = second priority
|
|
240
|
-
if (type ===
|
|
241
|
-
return { score: 2, typeName:
|
|
265
|
+
if (type === 'array' || schema.items) {
|
|
266
|
+
return { score: 2, typeName: 'array' };
|
|
242
267
|
}
|
|
243
268
|
// Any other non-null type
|
|
244
|
-
if (type && type !==
|
|
269
|
+
if (type && type !== 'null') {
|
|
245
270
|
return { score: 1, typeName: type };
|
|
246
271
|
}
|
|
247
272
|
// Null or no type
|
|
248
|
-
return { score: 0, typeName: type ||
|
|
273
|
+
return { score: 0, typeName: type || 'null' };
|
|
249
274
|
}
|
|
250
275
|
/**
|
|
251
276
|
* Checks if an anyOf/oneOf array represents enum choices.
|
|
@@ -262,7 +287,7 @@ function tryMergeEnumFromUnion(options) {
|
|
|
262
287
|
}
|
|
263
288
|
const enumValues = [];
|
|
264
289
|
for (const option of options) {
|
|
265
|
-
if (!option || typeof option !==
|
|
290
|
+
if (!option || typeof option !== 'object') {
|
|
266
291
|
return null;
|
|
267
292
|
}
|
|
268
293
|
// Check for const value
|
|
@@ -283,7 +308,11 @@ function tryMergeEnumFromUnion(options) {
|
|
|
283
308
|
continue;
|
|
284
309
|
}
|
|
285
310
|
// If option has complex structure (properties, items, etc.), it's not a simple enum
|
|
286
|
-
if (option.properties ||
|
|
311
|
+
if (option.properties ||
|
|
312
|
+
option.items ||
|
|
313
|
+
option.anyOf ||
|
|
314
|
+
option.oneOf ||
|
|
315
|
+
option.allOf) {
|
|
287
316
|
return null;
|
|
288
317
|
}
|
|
289
318
|
// If option has only type (no const/enum), it's not an enum pattern
|
|
@@ -304,18 +333,18 @@ function tryMergeEnumFromUnion(options) {
|
|
|
304
333
|
* → { type: "string", enum: ["a", "b"] }
|
|
305
334
|
*/
|
|
306
335
|
function flattenAnyOfOneOf(schema) {
|
|
307
|
-
if (!schema || typeof schema !==
|
|
336
|
+
if (!schema || typeof schema !== 'object') {
|
|
308
337
|
return schema;
|
|
309
338
|
}
|
|
310
339
|
if (Array.isArray(schema)) {
|
|
311
|
-
return schema.map(item => flattenAnyOfOneOf(item));
|
|
340
|
+
return schema.map((item) => flattenAnyOfOneOf(item));
|
|
312
341
|
}
|
|
313
342
|
let result = { ...schema };
|
|
314
343
|
// Process anyOf or oneOf
|
|
315
|
-
for (const unionKey of [
|
|
344
|
+
for (const unionKey of ['anyOf', 'oneOf']) {
|
|
316
345
|
if (Array.isArray(result[unionKey]) && result[unionKey].length > 0) {
|
|
317
346
|
const options = result[unionKey];
|
|
318
|
-
const parentDesc = typeof result.description ===
|
|
347
|
+
const parentDesc = typeof result.description === 'string' ? result.description : '';
|
|
319
348
|
// First, check if this is an enum pattern (anyOf with const/enum values)
|
|
320
349
|
// This is crucial for tools like WebFetch where format: anyOf[{const:"text"},{const:"markdown"},{const:"html"}]
|
|
321
350
|
const mergedEnum = tryMergeEnumFromUnion(options);
|
|
@@ -324,7 +353,7 @@ function flattenAnyOfOneOf(schema) {
|
|
|
324
353
|
const { [unionKey]: _, ...rest } = result;
|
|
325
354
|
result = {
|
|
326
355
|
...rest,
|
|
327
|
-
type:
|
|
356
|
+
type: 'string',
|
|
328
357
|
enum: mergedEnum,
|
|
329
358
|
};
|
|
330
359
|
// Preserve parent description
|
|
@@ -349,12 +378,15 @@ function flattenAnyOfOneOf(schema) {
|
|
|
349
378
|
}
|
|
350
379
|
}
|
|
351
380
|
// Select the best option and flatten it recursively
|
|
352
|
-
let selected = flattenAnyOfOneOf(options[bestIdx]) || { type:
|
|
381
|
+
let selected = flattenAnyOfOneOf(options[bestIdx]) || { type: 'string' };
|
|
353
382
|
// Preserve parent description
|
|
354
383
|
if (parentDesc) {
|
|
355
|
-
const childDesc = typeof selected.description ===
|
|
384
|
+
const childDesc = typeof selected.description === 'string' ? selected.description : '';
|
|
356
385
|
if (childDesc && childDesc !== parentDesc) {
|
|
357
|
-
selected = {
|
|
386
|
+
selected = {
|
|
387
|
+
...selected,
|
|
388
|
+
description: `${parentDesc} (${childDesc})`,
|
|
389
|
+
};
|
|
358
390
|
}
|
|
359
391
|
else if (!childDesc) {
|
|
360
392
|
selected = { ...selected, description: parentDesc };
|
|
@@ -362,7 +394,7 @@ function flattenAnyOfOneOf(schema) {
|
|
|
362
394
|
}
|
|
363
395
|
if (allTypes.length > 1) {
|
|
364
396
|
const uniqueTypes = Array.from(new Set(allTypes));
|
|
365
|
-
const hint = `Accepts: ${uniqueTypes.join(
|
|
397
|
+
const hint = `Accepts: ${uniqueTypes.join(' | ')}`;
|
|
366
398
|
selected = appendDescriptionHint(selected, hint);
|
|
367
399
|
}
|
|
368
400
|
// Replace result with selected schema, preserving other fields
|
|
@@ -372,7 +404,7 @@ function flattenAnyOfOneOf(schema) {
|
|
|
372
404
|
}
|
|
373
405
|
// Recursively process nested objects
|
|
374
406
|
for (const [key, value] of Object.entries(result)) {
|
|
375
|
-
if (typeof value ===
|
|
407
|
+
if (typeof value === 'object' && value !== null) {
|
|
376
408
|
result[key] = flattenAnyOfOneOf(value);
|
|
377
409
|
}
|
|
378
410
|
}
|
|
@@ -383,43 +415,46 @@ function flattenAnyOfOneOf(schema) {
|
|
|
383
415
|
* { type: ["string", "null"] } → { type: "string", description: "(nullable)" }
|
|
384
416
|
*/
|
|
385
417
|
function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
386
|
-
if (!schema || typeof schema !==
|
|
418
|
+
if (!schema || typeof schema !== 'object') {
|
|
387
419
|
return schema;
|
|
388
420
|
}
|
|
389
421
|
if (Array.isArray(schema)) {
|
|
390
|
-
return schema.map((item, idx) => flattenTypeArrays(item, nullableFields, `${currentPath ||
|
|
422
|
+
return schema.map((item, idx) => flattenTypeArrays(item, nullableFields, `${currentPath || ''}[${idx}]`));
|
|
391
423
|
}
|
|
392
424
|
let result = { ...schema };
|
|
393
425
|
const localNullableFields = nullableFields || new Map();
|
|
394
426
|
// Handle type array
|
|
395
427
|
if (Array.isArray(result.type)) {
|
|
396
428
|
const types = result.type;
|
|
397
|
-
const hasNull = types.includes(
|
|
398
|
-
const nonNullTypes = types.filter(t => t !==
|
|
429
|
+
const hasNull = types.includes('null');
|
|
430
|
+
const nonNullTypes = types.filter((t) => t !== 'null' && t);
|
|
399
431
|
// Select first non-null type, or "string" as fallback
|
|
400
|
-
const firstType = nonNullTypes.length > 0 ? nonNullTypes[0] :
|
|
432
|
+
const firstType = nonNullTypes.length > 0 ? nonNullTypes[0] : 'string';
|
|
401
433
|
result.type = firstType;
|
|
402
434
|
// Add hint for multiple types
|
|
403
435
|
if (nonNullTypes.length > 1) {
|
|
404
|
-
result = appendDescriptionHint(result, `Accepts: ${nonNullTypes.join(
|
|
436
|
+
result = appendDescriptionHint(result, `Accepts: ${nonNullTypes.join(' | ')}`);
|
|
405
437
|
}
|
|
406
438
|
// Add nullable hint
|
|
407
439
|
if (hasNull) {
|
|
408
|
-
result = appendDescriptionHint(result,
|
|
440
|
+
result = appendDescriptionHint(result, 'nullable');
|
|
409
441
|
}
|
|
410
442
|
}
|
|
411
443
|
// Recursively process properties
|
|
412
|
-
if (result.properties && typeof result.properties ===
|
|
444
|
+
if (result.properties && typeof result.properties === 'object') {
|
|
413
445
|
const newProps = {};
|
|
414
446
|
for (const [propKey, propValue] of Object.entries(result.properties)) {
|
|
415
|
-
const propPath = currentPath
|
|
447
|
+
const propPath = currentPath
|
|
448
|
+
? `${currentPath}.properties.${propKey}`
|
|
449
|
+
: `properties.${propKey}`;
|
|
416
450
|
const processed = flattenTypeArrays(propValue, localNullableFields, propPath);
|
|
417
451
|
newProps[propKey] = processed;
|
|
418
452
|
// Track nullable fields for required array cleanup
|
|
419
|
-
if (processed &&
|
|
420
|
-
typeof processed
|
|
421
|
-
processed.description
|
|
422
|
-
|
|
453
|
+
if (processed &&
|
|
454
|
+
typeof processed === 'object' &&
|
|
455
|
+
typeof processed.description === 'string' &&
|
|
456
|
+
processed.description.includes('nullable')) {
|
|
457
|
+
const objectPath = currentPath || '';
|
|
423
458
|
const existing = localNullableFields.get(objectPath) || [];
|
|
424
459
|
existing.push(propKey);
|
|
425
460
|
localNullableFields.set(objectPath, existing);
|
|
@@ -430,7 +465,7 @@ function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
|
430
465
|
// Remove nullable fields from required array
|
|
431
466
|
if (Array.isArray(result.required) && !nullableFields) {
|
|
432
467
|
// Only at root level, filter out nullable fields
|
|
433
|
-
const nullableAtRoot = localNullableFields.get(
|
|
468
|
+
const nullableAtRoot = localNullableFields.get('') || [];
|
|
434
469
|
if (nullableAtRoot.length > 0) {
|
|
435
470
|
result.required = result.required.filter((r) => !nullableAtRoot.includes(r));
|
|
436
471
|
if (result.required.length === 0) {
|
|
@@ -440,8 +475,8 @@ function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
|
440
475
|
}
|
|
441
476
|
// Recursively process other nested objects
|
|
442
477
|
for (const [key, value] of Object.entries(result)) {
|
|
443
|
-
if (key !==
|
|
444
|
-
result[key] = flattenTypeArrays(value, localNullableFields, `${currentPath ||
|
|
478
|
+
if (key !== 'properties' && typeof value === 'object' && value !== null) {
|
|
479
|
+
result[key] = flattenTypeArrays(value, localNullableFields, `${currentPath || ''}.${key}`);
|
|
445
480
|
}
|
|
446
481
|
}
|
|
447
482
|
return result;
|
|
@@ -451,19 +486,20 @@ function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
|
451
486
|
* @param insideProperties - When true, keys are property NAMES (preserve); when false, keys are JSON Schema keywords (filter).
|
|
452
487
|
*/
|
|
453
488
|
function removeUnsupportedKeywords(schema, insideProperties = false) {
|
|
454
|
-
if (!schema || typeof schema !==
|
|
489
|
+
if (!schema || typeof schema !== 'object') {
|
|
455
490
|
return schema;
|
|
456
491
|
}
|
|
457
492
|
if (Array.isArray(schema)) {
|
|
458
|
-
return schema.map(item => removeUnsupportedKeywords(item, false));
|
|
493
|
+
return schema.map((item) => removeUnsupportedKeywords(item, false));
|
|
459
494
|
}
|
|
460
495
|
const result = {};
|
|
461
496
|
for (const [key, value] of Object.entries(schema)) {
|
|
462
|
-
if (!insideProperties &&
|
|
497
|
+
if (!insideProperties &&
|
|
498
|
+
UNSUPPORTED_KEYWORDS.includes(key)) {
|
|
463
499
|
continue;
|
|
464
500
|
}
|
|
465
|
-
if (typeof value ===
|
|
466
|
-
if (key ===
|
|
501
|
+
if (typeof value === 'object' && value !== null) {
|
|
502
|
+
if (key === 'properties') {
|
|
467
503
|
const propertiesResult = {};
|
|
468
504
|
for (const [propName, propSchema] of Object.entries(value)) {
|
|
469
505
|
propertiesResult[propName] = removeUnsupportedKeywords(propSchema, false);
|
|
@@ -484,16 +520,18 @@ function removeUnsupportedKeywords(schema, insideProperties = false) {
|
|
|
484
520
|
* Phase 3b: Cleans up required fields - removes entries that don't exist in properties.
|
|
485
521
|
*/
|
|
486
522
|
function cleanupRequiredFields(schema) {
|
|
487
|
-
if (!schema || typeof schema !==
|
|
523
|
+
if (!schema || typeof schema !== 'object') {
|
|
488
524
|
return schema;
|
|
489
525
|
}
|
|
490
526
|
if (Array.isArray(schema)) {
|
|
491
|
-
return schema.map(item => cleanupRequiredFields(item));
|
|
527
|
+
return schema.map((item) => cleanupRequiredFields(item));
|
|
492
528
|
}
|
|
493
|
-
|
|
529
|
+
const result = { ...schema };
|
|
494
530
|
// Clean up required array if properties exist
|
|
495
|
-
if (Array.isArray(result.required) &&
|
|
496
|
-
|
|
531
|
+
if (Array.isArray(result.required) &&
|
|
532
|
+
result.properties &&
|
|
533
|
+
typeof result.properties === 'object') {
|
|
534
|
+
const validRequired = result.required.filter((req) => Object.hasOwn(result.properties, req));
|
|
497
535
|
if (validRequired.length === 0) {
|
|
498
536
|
delete result.required;
|
|
499
537
|
}
|
|
@@ -503,7 +541,7 @@ function cleanupRequiredFields(schema) {
|
|
|
503
541
|
}
|
|
504
542
|
// Recursively process nested objects
|
|
505
543
|
for (const [key, value] of Object.entries(result)) {
|
|
506
|
-
if (typeof value ===
|
|
544
|
+
if (typeof value === 'object' && value !== null) {
|
|
507
545
|
result[key] = cleanupRequiredFields(value);
|
|
508
546
|
}
|
|
509
547
|
}
|
|
@@ -514,23 +552,23 @@ function cleanupRequiredFields(schema) {
|
|
|
514
552
|
* Claude VALIDATED mode requires at least one property.
|
|
515
553
|
*/
|
|
516
554
|
function addEmptySchemaPlaceholder(schema) {
|
|
517
|
-
if (!schema || typeof schema !==
|
|
555
|
+
if (!schema || typeof schema !== 'object') {
|
|
518
556
|
return schema;
|
|
519
557
|
}
|
|
520
558
|
if (Array.isArray(schema)) {
|
|
521
|
-
return schema.map(item => addEmptySchemaPlaceholder(item));
|
|
559
|
+
return schema.map((item) => addEmptySchemaPlaceholder(item));
|
|
522
560
|
}
|
|
523
|
-
|
|
561
|
+
const result = { ...schema };
|
|
524
562
|
// Check if this is an empty object schema
|
|
525
|
-
const isObjectType = result.type ===
|
|
563
|
+
const isObjectType = result.type === 'object';
|
|
526
564
|
if (isObjectType) {
|
|
527
565
|
const hasProperties = result.properties &&
|
|
528
|
-
typeof result.properties ===
|
|
566
|
+
typeof result.properties === 'object' &&
|
|
529
567
|
Object.keys(result.properties).length > 0;
|
|
530
568
|
if (!hasProperties) {
|
|
531
569
|
result.properties = {
|
|
532
570
|
[EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
|
|
533
|
-
type:
|
|
571
|
+
type: 'boolean',
|
|
534
572
|
description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
|
|
535
573
|
},
|
|
536
574
|
};
|
|
@@ -539,7 +577,7 @@ function addEmptySchemaPlaceholder(schema) {
|
|
|
539
577
|
}
|
|
540
578
|
// Recursively process nested objects
|
|
541
579
|
for (const [key, value] of Object.entries(result)) {
|
|
542
|
-
if (typeof value ===
|
|
580
|
+
if (typeof value === 'object' && value !== null) {
|
|
543
581
|
result[key] = addEmptySchemaPlaceholder(value);
|
|
544
582
|
}
|
|
545
583
|
}
|
|
@@ -552,7 +590,7 @@ function addEmptySchemaPlaceholder(schema) {
|
|
|
552
590
|
* Ported from CLIProxyAPI's CleanJSONSchemaForAntigravity (gemini_schema.go)
|
|
553
591
|
*/
|
|
554
592
|
export function cleanJSONSchemaForAntigravity(schema) {
|
|
555
|
-
if (!schema || typeof schema !==
|
|
593
|
+
if (!schema || typeof schema !== 'object') {
|
|
556
594
|
return schema;
|
|
557
595
|
}
|
|
558
596
|
let result = schema;
|
|
@@ -584,33 +622,37 @@ export const DEFAULT_THINKING_BUDGET = 16000;
|
|
|
584
622
|
*/
|
|
585
623
|
export function isThinkingCapableModel(modelName) {
|
|
586
624
|
const lowerModel = modelName.toLowerCase();
|
|
587
|
-
return lowerModel.includes(
|
|
588
|
-
|
|
589
|
-
|
|
625
|
+
return (lowerModel.includes('thinking') ||
|
|
626
|
+
lowerModel.includes('gemini-3') ||
|
|
627
|
+
lowerModel.includes('opus'));
|
|
590
628
|
}
|
|
591
629
|
/**
|
|
592
630
|
* Extracts thinking configuration from various possible request locations.
|
|
593
631
|
* Supports both Gemini-style thinkingConfig and Anthropic-style thinking options.
|
|
594
632
|
*/
|
|
595
633
|
export function extractThinkingConfig(requestPayload, rawGenerationConfig, extraBody) {
|
|
596
|
-
const thinkingConfig = rawGenerationConfig?.thinkingConfig
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (thinkingConfig && typeof thinkingConfig ===
|
|
634
|
+
const thinkingConfig = rawGenerationConfig?.thinkingConfig ??
|
|
635
|
+
extraBody?.thinkingConfig ??
|
|
636
|
+
requestPayload.thinkingConfig;
|
|
637
|
+
if (thinkingConfig && typeof thinkingConfig === 'object') {
|
|
600
638
|
const config = thinkingConfig;
|
|
601
639
|
return {
|
|
602
640
|
includeThoughts: Boolean(config.includeThoughts),
|
|
603
|
-
thinkingBudget: typeof config.thinkingBudget ===
|
|
641
|
+
thinkingBudget: typeof config.thinkingBudget === 'number'
|
|
642
|
+
? config.thinkingBudget
|
|
643
|
+
: DEFAULT_THINKING_BUDGET,
|
|
604
644
|
};
|
|
605
645
|
}
|
|
606
646
|
// Convert Anthropic-style "thinking" option: { type: "enabled", budgetTokens: N }
|
|
607
647
|
const anthropicThinking = extraBody?.thinking ?? requestPayload.thinking;
|
|
608
|
-
if (anthropicThinking && typeof anthropicThinking ===
|
|
648
|
+
if (anthropicThinking && typeof anthropicThinking === 'object') {
|
|
609
649
|
const thinking = anthropicThinking;
|
|
610
|
-
if (thinking.type ===
|
|
650
|
+
if (thinking.type === 'enabled' || thinking.budgetTokens) {
|
|
611
651
|
return {
|
|
612
652
|
includeThoughts: true,
|
|
613
|
-
thinkingBudget: typeof thinking.budgetTokens ===
|
|
653
|
+
thinkingBudget: typeof thinking.budgetTokens === 'number'
|
|
654
|
+
? thinking.budgetTokens
|
|
655
|
+
: DEFAULT_THINKING_BUDGET,
|
|
614
656
|
};
|
|
615
657
|
}
|
|
616
658
|
}
|
|
@@ -632,42 +674,54 @@ export function extractThinkingConfig(requestPayload, rawGenerationConfig, extra
|
|
|
632
674
|
export function extractVariantThinkingConfig(providerOptions, generationConfig) {
|
|
633
675
|
const result = {};
|
|
634
676
|
// Primary path: extract from providerOptions.google
|
|
635
|
-
const google =
|
|
677
|
+
const google = providerOptions?.google;
|
|
636
678
|
if (google) {
|
|
637
679
|
// Gemini 3 native format: { google: { thinkingLevel: "high", includeThoughts: true } }
|
|
638
680
|
// thinkingLevel takes priority over thinkingBudget - they are mutually exclusive
|
|
639
|
-
if (typeof google.thinkingLevel ===
|
|
681
|
+
if (typeof google.thinkingLevel === 'string') {
|
|
640
682
|
result.thinkingLevel = google.thinkingLevel;
|
|
641
|
-
result.includeThoughts =
|
|
683
|
+
result.includeThoughts =
|
|
684
|
+
typeof google.includeThoughts === 'boolean'
|
|
685
|
+
? google.includeThoughts
|
|
686
|
+
: undefined;
|
|
642
687
|
}
|
|
643
|
-
else if (google.thinkingConfig &&
|
|
688
|
+
else if (google.thinkingConfig &&
|
|
689
|
+
typeof google.thinkingConfig === 'object') {
|
|
644
690
|
// Budget-based format (Claude/Gemini 2.5): { google: { thinkingConfig: { thinkingBudget } } }
|
|
645
691
|
// Only used when thinkingLevel is not present
|
|
646
692
|
const tc = google.thinkingConfig;
|
|
647
|
-
if (typeof tc.thinkingBudget ===
|
|
693
|
+
if (typeof tc.thinkingBudget === 'number') {
|
|
648
694
|
result.thinkingBudget = tc.thinkingBudget;
|
|
649
695
|
}
|
|
650
696
|
}
|
|
651
697
|
// Extract Google Search config
|
|
652
|
-
if (google.googleSearch && typeof google.googleSearch ===
|
|
698
|
+
if (google.googleSearch && typeof google.googleSearch === 'object') {
|
|
653
699
|
const search = google.googleSearch;
|
|
654
700
|
result.googleSearch = {
|
|
655
|
-
mode: search.mode === 'auto' || search.mode === 'off'
|
|
701
|
+
mode: search.mode === 'auto' || search.mode === 'off'
|
|
702
|
+
? search.mode
|
|
703
|
+
: undefined,
|
|
656
704
|
threshold: typeof search.threshold === 'number' ? search.threshold : undefined,
|
|
657
705
|
};
|
|
658
706
|
}
|
|
659
707
|
}
|
|
660
708
|
// Fallback: OpenCode may pass thinking config in generationConfig
|
|
661
709
|
// instead of providerOptions (common when using model variants)
|
|
662
|
-
if (result.thinkingBudget === undefined &&
|
|
663
|
-
|
|
710
|
+
if (result.thinkingBudget === undefined &&
|
|
711
|
+
!result.thinkingLevel &&
|
|
712
|
+
generationConfig) {
|
|
713
|
+
if (generationConfig.thinkingConfig &&
|
|
714
|
+
typeof generationConfig.thinkingConfig === 'object') {
|
|
664
715
|
const tc = generationConfig.thinkingConfig;
|
|
665
|
-
if (typeof tc.thinkingLevel ===
|
|
716
|
+
if (typeof tc.thinkingLevel === 'string') {
|
|
666
717
|
// Gemini 3 native format sent via generationConfig
|
|
667
718
|
result.thinkingLevel = tc.thinkingLevel;
|
|
668
|
-
result.includeThoughts =
|
|
719
|
+
result.includeThoughts =
|
|
720
|
+
typeof tc.includeThoughts === 'boolean'
|
|
721
|
+
? tc.includeThoughts
|
|
722
|
+
: undefined;
|
|
669
723
|
}
|
|
670
|
-
else if (typeof tc.thinkingBudget ===
|
|
724
|
+
else if (typeof tc.thinkingBudget === 'number') {
|
|
671
725
|
result.thinkingBudget = tc.thinkingBudget;
|
|
672
726
|
}
|
|
673
727
|
}
|
|
@@ -691,11 +745,11 @@ export function resolveThinkingConfig(userConfig, isThinkingModel, _isClaudeMode
|
|
|
691
745
|
* Checks if a part is a thinking/reasoning block (Anthropic or Gemini style).
|
|
692
746
|
*/
|
|
693
747
|
function isThinkingPart(part) {
|
|
694
|
-
return part.type ===
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
748
|
+
return (part.type === 'thinking' ||
|
|
749
|
+
part.type === 'redacted_thinking' ||
|
|
750
|
+
part.type === 'reasoning' ||
|
|
751
|
+
part.thinking !== undefined ||
|
|
752
|
+
part.thought === true);
|
|
699
753
|
}
|
|
700
754
|
/**
|
|
701
755
|
* Checks if a part has a signature field (thinking block signature).
|
|
@@ -713,15 +767,15 @@ function hasSignatureField(part) {
|
|
|
713
767
|
* - Gemini: { functionCall }, { functionResponse }
|
|
714
768
|
*/
|
|
715
769
|
function isToolBlock(part) {
|
|
716
|
-
return part.type ===
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
770
|
+
return (part.type === 'tool_use' ||
|
|
771
|
+
part.type === 'tool_result' ||
|
|
772
|
+
part.tool_use_id !== undefined ||
|
|
773
|
+
part.tool_call_id !== undefined ||
|
|
774
|
+
part.tool_result !== undefined ||
|
|
775
|
+
part.tool_use !== undefined ||
|
|
776
|
+
part.toolUse !== undefined ||
|
|
777
|
+
part.functionCall !== undefined ||
|
|
778
|
+
part.functionResponse !== undefined);
|
|
725
779
|
}
|
|
726
780
|
/**
|
|
727
781
|
* Unconditionally strips ALL thinking/reasoning blocks from a content array.
|
|
@@ -729,8 +783,8 @@ function isToolBlock(part) {
|
|
|
729
783
|
* Claude will generate fresh thinking for each turn.
|
|
730
784
|
*/
|
|
731
785
|
function stripAllThinkingBlocks(contentArray) {
|
|
732
|
-
return contentArray.map(item => {
|
|
733
|
-
if (!item || typeof item !==
|
|
786
|
+
return contentArray.map((item) => {
|
|
787
|
+
if (!item || typeof item !== 'object')
|
|
734
788
|
return item;
|
|
735
789
|
if (isToolBlock(item))
|
|
736
790
|
return item;
|
|
@@ -739,7 +793,7 @@ function stripAllThinkingBlocks(contentArray) {
|
|
|
739
793
|
const cc = item.cache_control;
|
|
740
794
|
// Use plain empty text part — thinking-format sentinels get converted by the proxy
|
|
741
795
|
// into Claude thinking blocks missing the required `thinking` field.
|
|
742
|
-
const sentinel = { text:
|
|
796
|
+
const sentinel = { text: '.' };
|
|
743
797
|
if (cc)
|
|
744
798
|
sentinel.cache_control = cc;
|
|
745
799
|
return sentinel;
|
|
@@ -763,7 +817,7 @@ function removeTrailingThinkingBlocks(contentArray, sessionId, getCachedSignatur
|
|
|
763
817
|
break;
|
|
764
818
|
// Replace with sentinel instead of popping — preserves array length
|
|
765
819
|
const cc = part?.cache_control;
|
|
766
|
-
const sentinel = { text:
|
|
820
|
+
const sentinel = { text: '.' };
|
|
767
821
|
if (cc)
|
|
768
822
|
sentinel.cache_control = cc;
|
|
769
823
|
result[i] = sentinel;
|
|
@@ -776,14 +830,14 @@ function removeTrailingThinkingBlocks(contentArray, sessionId, getCachedSignatur
|
|
|
776
830
|
*/
|
|
777
831
|
function hasValidSignature(part) {
|
|
778
832
|
const signature = part.thought === true ? part.thoughtSignature : part.signature;
|
|
779
|
-
return typeof signature ===
|
|
833
|
+
return typeof signature === 'string' && signature.length >= 50;
|
|
780
834
|
}
|
|
781
835
|
/**
|
|
782
836
|
* Gets the signature from a thinking part, if present.
|
|
783
837
|
*/
|
|
784
838
|
function getSignature(part) {
|
|
785
839
|
const signature = part.thought === true ? part.thoughtSignature : part.signature;
|
|
786
|
-
return typeof signature ===
|
|
840
|
+
return typeof signature === 'string' ? signature : undefined;
|
|
787
841
|
}
|
|
788
842
|
/**
|
|
789
843
|
* Checks if a thinking part's signature was generated by our plugin (exists in our cache).
|
|
@@ -809,21 +863,21 @@ function isOurCachedSignature(part, sessionId, getCachedSignatureFn) {
|
|
|
809
863
|
* Gets the text content from a thinking part.
|
|
810
864
|
*/
|
|
811
865
|
function getThinkingText(part) {
|
|
812
|
-
if (typeof part.text ===
|
|
866
|
+
if (typeof part.text === 'string')
|
|
813
867
|
return part.text;
|
|
814
|
-
if (typeof part.thinking ===
|
|
868
|
+
if (typeof part.thinking === 'string')
|
|
815
869
|
return part.thinking;
|
|
816
|
-
if (part.text && typeof part.text ===
|
|
870
|
+
if (part.text && typeof part.text === 'object') {
|
|
817
871
|
const maybeText = part.text.text;
|
|
818
|
-
if (typeof maybeText ===
|
|
872
|
+
if (typeof maybeText === 'string')
|
|
819
873
|
return maybeText;
|
|
820
874
|
}
|
|
821
|
-
if (part.thinking && typeof part.thinking ===
|
|
875
|
+
if (part.thinking && typeof part.thinking === 'object') {
|
|
822
876
|
const maybeText = part.thinking.text ?? part.thinking.thinking;
|
|
823
|
-
if (typeof maybeText ===
|
|
877
|
+
if (typeof maybeText === 'string')
|
|
824
878
|
return maybeText;
|
|
825
879
|
}
|
|
826
|
-
return
|
|
880
|
+
return '';
|
|
827
881
|
}
|
|
828
882
|
/**
|
|
829
883
|
* Recursively strips cache_control and providerOptions from any object.
|
|
@@ -832,13 +886,13 @@ function getThinkingText(part) {
|
|
|
832
886
|
function stripCacheControlRecursively(obj) {
|
|
833
887
|
if (obj === null || obj === undefined)
|
|
834
888
|
return obj;
|
|
835
|
-
if (typeof obj !==
|
|
889
|
+
if (typeof obj !== 'object')
|
|
836
890
|
return obj;
|
|
837
891
|
if (Array.isArray(obj))
|
|
838
|
-
return obj.map(item => stripCacheControlRecursively(item));
|
|
892
|
+
return obj.map((item) => stripCacheControlRecursively(item));
|
|
839
893
|
const result = {};
|
|
840
894
|
for (const [key, value] of Object.entries(obj)) {
|
|
841
|
-
if (key ===
|
|
895
|
+
if (key === 'cache_control' || key === 'providerOptions')
|
|
842
896
|
continue;
|
|
843
897
|
result[key] = stripCacheControlRecursively(value);
|
|
844
898
|
}
|
|
@@ -853,16 +907,16 @@ function sanitizeThinkingPart(part) {
|
|
|
853
907
|
// Gemini-style thought blocks: { thought: true, text, thoughtSignature }
|
|
854
908
|
if (part.thought === true) {
|
|
855
909
|
let textContent = part.text;
|
|
856
|
-
if (typeof textContent ===
|
|
910
|
+
if (typeof textContent === 'object' && textContent !== null) {
|
|
857
911
|
const maybeText = textContent.text;
|
|
858
|
-
textContent = typeof maybeText ===
|
|
912
|
+
textContent = typeof maybeText === 'string' ? maybeText : undefined;
|
|
859
913
|
}
|
|
860
|
-
const hasContent = typeof textContent ===
|
|
914
|
+
const hasContent = typeof textContent === 'string' && textContent.trim().length > 0;
|
|
861
915
|
if (!hasContent && !part.thoughtSignature) {
|
|
862
916
|
return null;
|
|
863
917
|
}
|
|
864
918
|
const sanitized = { thought: true };
|
|
865
|
-
sanitized.text = typeof textContent ===
|
|
919
|
+
sanitized.text = typeof textContent === 'string' ? textContent : '';
|
|
866
920
|
if (part.thoughtSignature !== undefined)
|
|
867
921
|
sanitized.thoughtSignature = part.thoughtSignature;
|
|
868
922
|
if (part.cache_control !== undefined)
|
|
@@ -870,18 +924,25 @@ function sanitizeThinkingPart(part) {
|
|
|
870
924
|
return sanitized;
|
|
871
925
|
}
|
|
872
926
|
// Anthropic-style thinking/redacted_thinking blocks: { type: "thinking"|"redacted_thinking", thinking, signature }
|
|
873
|
-
if (part.type ===
|
|
927
|
+
if (part.type === 'thinking' ||
|
|
928
|
+
part.type === 'redacted_thinking' ||
|
|
929
|
+
part.thinking !== undefined) {
|
|
874
930
|
let thinkingContent = part.thinking ?? part.text;
|
|
875
|
-
if (thinkingContent !== undefined &&
|
|
931
|
+
if (thinkingContent !== undefined &&
|
|
932
|
+
typeof thinkingContent === 'object' &&
|
|
933
|
+
thinkingContent !== null) {
|
|
876
934
|
const maybeText = thinkingContent.text ?? thinkingContent.thinking;
|
|
877
|
-
thinkingContent = typeof maybeText ===
|
|
935
|
+
thinkingContent = typeof maybeText === 'string' ? maybeText : undefined;
|
|
878
936
|
}
|
|
879
|
-
const hasContent = typeof thinkingContent ===
|
|
937
|
+
const hasContent = typeof thinkingContent === 'string' && thinkingContent.trim().length > 0;
|
|
880
938
|
if (!hasContent && !part.signature) {
|
|
881
939
|
return null;
|
|
882
940
|
}
|
|
883
|
-
const sanitized = {
|
|
884
|
-
|
|
941
|
+
const sanitized = {
|
|
942
|
+
type: part.type === 'redacted_thinking' ? 'redacted_thinking' : 'thinking',
|
|
943
|
+
};
|
|
944
|
+
sanitized.thinking =
|
|
945
|
+
typeof thinkingContent === 'string' ? thinkingContent : '';
|
|
885
946
|
if (part.signature !== undefined)
|
|
886
947
|
sanitized.signature = part.signature;
|
|
887
948
|
if (part.cache_control !== undefined)
|
|
@@ -889,18 +950,18 @@ function sanitizeThinkingPart(part) {
|
|
|
889
950
|
return sanitized;
|
|
890
951
|
}
|
|
891
952
|
// Reasoning blocks (OpenCode format): { type: "reasoning", text, signature }
|
|
892
|
-
if (part.type ===
|
|
953
|
+
if (part.type === 'reasoning') {
|
|
893
954
|
let textContent = part.text;
|
|
894
|
-
if (typeof textContent ===
|
|
955
|
+
if (typeof textContent === 'object' && textContent !== null) {
|
|
895
956
|
const maybeText = textContent.text;
|
|
896
|
-
textContent = typeof maybeText ===
|
|
957
|
+
textContent = typeof maybeText === 'string' ? maybeText : undefined;
|
|
897
958
|
}
|
|
898
|
-
const hasContent = typeof textContent ===
|
|
959
|
+
const hasContent = typeof textContent === 'string' && textContent.trim().length > 0;
|
|
899
960
|
if (!hasContent && !part.signature) {
|
|
900
961
|
return null;
|
|
901
962
|
}
|
|
902
|
-
const sanitized = { type:
|
|
903
|
-
sanitized.text = typeof textContent ===
|
|
963
|
+
const sanitized = { type: 'reasoning' };
|
|
964
|
+
sanitized.text = typeof textContent === 'string' ? textContent : '';
|
|
904
965
|
if (part.signature !== undefined)
|
|
905
966
|
sanitized.signature = part.signature;
|
|
906
967
|
if (part.cache_control !== undefined)
|
|
@@ -914,7 +975,7 @@ function sanitizeThinkingPart(part) {
|
|
|
914
975
|
function findLastAssistantIndex(contents, roleValue) {
|
|
915
976
|
for (let i = contents.length - 1; i >= 0; i--) {
|
|
916
977
|
const content = contents[i];
|
|
917
|
-
if (content && typeof content ===
|
|
978
|
+
if (content && typeof content === 'object' && content.role === roleValue) {
|
|
918
979
|
return i;
|
|
919
980
|
}
|
|
920
981
|
}
|
|
@@ -928,7 +989,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
928
989
|
}
|
|
929
990
|
const filtered = [];
|
|
930
991
|
for (const item of contentArray) {
|
|
931
|
-
if (!item || typeof item !==
|
|
992
|
+
if (!item || typeof item !== 'object') {
|
|
932
993
|
filtered.push(item);
|
|
933
994
|
continue;
|
|
934
995
|
}
|
|
@@ -954,7 +1015,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
954
1015
|
if (isClaudeModel && (isThinking || hasSignature)) {
|
|
955
1016
|
// Use plain empty text part — thinking-format sentinels get converted by the proxy
|
|
956
1017
|
// into Claude thinking blocks with missing required fields
|
|
957
|
-
const sentinel = { text:
|
|
1018
|
+
const sentinel = { text: '.' };
|
|
958
1019
|
if (item.cache_control)
|
|
959
1020
|
sentinel.cache_control = item.cache_control;
|
|
960
1021
|
filtered.push(sentinel);
|
|
@@ -974,7 +1035,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
974
1035
|
}
|
|
975
1036
|
else {
|
|
976
1037
|
// sanitizeThinkingPart returned null — use sentinel to preserve array length
|
|
977
|
-
const sentinel = { text:
|
|
1038
|
+
const sentinel = { text: '.' };
|
|
978
1039
|
if (item.cache_control)
|
|
979
1040
|
sentinel.cache_control = item.cache_control;
|
|
980
1041
|
filtered.push(sentinel);
|
|
@@ -983,9 +1044,11 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
983
1044
|
}
|
|
984
1045
|
// Not our signature (or no signature) - use plain empty text sentinel // Thinking-format sentinels get converted by the proxy into Claude thinking blocks with missing required fields
|
|
985
1046
|
const existingSignature = item.signature || item.thoughtSignature;
|
|
986
|
-
const signatureInfo = existingSignature
|
|
1047
|
+
const signatureInfo = existingSignature
|
|
1048
|
+
? `foreign signature (${String(existingSignature).length} chars)`
|
|
1049
|
+
: 'no signature';
|
|
987
1050
|
log.debug(`Injecting plain text sentinel for last-message thinking block with ${signatureInfo}`);
|
|
988
|
-
const sentinel = { text:
|
|
1051
|
+
const sentinel = { text: '.' };
|
|
989
1052
|
if (item.cache_control)
|
|
990
1053
|
sentinel.cache_control = item.cache_control;
|
|
991
1054
|
filtered.push(sentinel);
|
|
@@ -998,7 +1061,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
998
1061
|
}
|
|
999
1062
|
else {
|
|
1000
1063
|
// sanitizeThinkingPart returned null — use sentinel to preserve array length
|
|
1001
|
-
const sentinel = { text:
|
|
1064
|
+
const sentinel = { text: '.' };
|
|
1002
1065
|
if (item.cache_control)
|
|
1003
1066
|
sentinel.cache_control = item.cache_control;
|
|
1004
1067
|
filtered.push(sentinel);
|
|
@@ -1012,9 +1075,11 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
1012
1075
|
if (cachedSignature && cachedSignature.length >= 50) {
|
|
1013
1076
|
const restoredPart = { ...item };
|
|
1014
1077
|
if (item.thought === true) {
|
|
1078
|
+
;
|
|
1015
1079
|
restoredPart.thoughtSignature = cachedSignature;
|
|
1016
1080
|
}
|
|
1017
1081
|
else {
|
|
1082
|
+
;
|
|
1018
1083
|
restoredPart.signature = cachedSignature;
|
|
1019
1084
|
}
|
|
1020
1085
|
const sanitized = sanitizeThinkingPart(restoredPart);
|
|
@@ -1023,7 +1088,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
1023
1088
|
}
|
|
1024
1089
|
else {
|
|
1025
1090
|
// sanitizeThinkingPart returned null — use sentinel to preserve array length
|
|
1026
|
-
const sentinel = { text:
|
|
1091
|
+
const sentinel = { text: '.' };
|
|
1027
1092
|
if (item.cache_control)
|
|
1028
1093
|
sentinel.cache_control = item.cache_control;
|
|
1029
1094
|
filtered.push(sentinel);
|
|
@@ -1034,7 +1099,7 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
1034
1099
|
}
|
|
1035
1100
|
// Catch-all: thinking/signature part that didn't match any branch above
|
|
1036
1101
|
// Use sentinel instead of silently dropping to preserve array length
|
|
1037
|
-
const sentinel = { text:
|
|
1102
|
+
const sentinel = { text: '.' };
|
|
1038
1103
|
if (item.cache_control)
|
|
1039
1104
|
sentinel.cache_control = item.cache_control;
|
|
1040
1105
|
filtered.push(sentinel);
|
|
@@ -1050,23 +1115,24 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
1050
1115
|
* @param getCachedSignatureFn - Optional function to retrieve cached signatures
|
|
1051
1116
|
*/
|
|
1052
1117
|
export function filterUnsignedThinkingBlocks(contents, sessionId, getCachedSignatureFn, isClaudeModel) {
|
|
1053
|
-
const lastAssistantIdx = findLastAssistantIndex(contents,
|
|
1118
|
+
const lastAssistantIdx = findLastAssistantIndex(contents, 'model');
|
|
1054
1119
|
return contents.map((content, idx) => {
|
|
1055
|
-
if (!content || typeof content !==
|
|
1120
|
+
if (!content || typeof content !== 'object') {
|
|
1056
1121
|
return content;
|
|
1057
1122
|
}
|
|
1058
1123
|
const isLastAssistant = idx === lastAssistantIdx;
|
|
1059
1124
|
if (Array.isArray(content.parts)) {
|
|
1060
1125
|
const filteredParts = filterContentArray(content.parts, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistant);
|
|
1061
|
-
const trimmedParts = content.role ===
|
|
1126
|
+
const trimmedParts = content.role === 'model' && !isClaudeModel
|
|
1062
1127
|
? removeTrailingThinkingBlocks(filteredParts, sessionId, getCachedSignatureFn)
|
|
1063
1128
|
: filteredParts;
|
|
1064
1129
|
return { ...content, parts: trimmedParts };
|
|
1065
1130
|
}
|
|
1066
1131
|
if (Array.isArray(content.content)) {
|
|
1067
|
-
const isAssistantRole = content.role ===
|
|
1132
|
+
const isAssistantRole = content.role === 'assistant';
|
|
1068
1133
|
const isLastAssistantContent = idx === lastAssistantIdx ||
|
|
1069
|
-
(isAssistantRole &&
|
|
1134
|
+
(isAssistantRole &&
|
|
1135
|
+
idx === findLastAssistantIndex(contents, 'assistant'));
|
|
1070
1136
|
const filteredContent = filterContentArray(content.content, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistantContent);
|
|
1071
1137
|
const trimmedContent = isAssistantRole && !isClaudeModel
|
|
1072
1138
|
? removeTrailingThinkingBlocks(filteredContent, sessionId, getCachedSignatureFn)
|
|
@@ -1080,13 +1146,13 @@ export function filterUnsignedThinkingBlocks(contents, sessionId, getCachedSigna
|
|
|
1080
1146
|
* Filters thinking blocks from Anthropic-style messages[] payloads using cached signatures.
|
|
1081
1147
|
*/
|
|
1082
1148
|
export function filterMessagesThinkingBlocks(messages, sessionId, getCachedSignatureFn, isClaudeModel) {
|
|
1083
|
-
const lastAssistantIdx = findLastAssistantIndex(messages,
|
|
1149
|
+
const lastAssistantIdx = findLastAssistantIndex(messages, 'assistant');
|
|
1084
1150
|
return messages.map((message, idx) => {
|
|
1085
|
-
if (!message || typeof message !==
|
|
1151
|
+
if (!message || typeof message !== 'object') {
|
|
1086
1152
|
return message;
|
|
1087
1153
|
}
|
|
1088
1154
|
if (Array.isArray(message.content)) {
|
|
1089
|
-
const isAssistantRole = message.role ===
|
|
1155
|
+
const isAssistantRole = message.role === 'assistant';
|
|
1090
1156
|
const isLastAssistant = isAssistantRole && idx === lastAssistantIdx;
|
|
1091
1157
|
const filteredContent = filterContentArray(message.content, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistant);
|
|
1092
1158
|
const trimmedContent = isAssistantRole && !isClaudeModel
|
|
@@ -1100,7 +1166,7 @@ export function filterMessagesThinkingBlocks(messages, sessionId, getCachedSigna
|
|
|
1100
1166
|
export function deepFilterThinkingBlocks(payload, sessionId, getCachedSignatureFn, isClaudeModel) {
|
|
1101
1167
|
const visited = new WeakSet();
|
|
1102
1168
|
const walk = (value) => {
|
|
1103
|
-
if (!value || typeof value !==
|
|
1169
|
+
if (!value || typeof value !== 'object') {
|
|
1104
1170
|
return;
|
|
1105
1171
|
}
|
|
1106
1172
|
if (visited.has(value)) {
|
|
@@ -1108,7 +1174,9 @@ export function deepFilterThinkingBlocks(payload, sessionId, getCachedSignatureF
|
|
|
1108
1174
|
}
|
|
1109
1175
|
visited.add(value);
|
|
1110
1176
|
if (Array.isArray(value)) {
|
|
1111
|
-
value.forEach((item) =>
|
|
1177
|
+
value.forEach((item) => {
|
|
1178
|
+
walk(item);
|
|
1179
|
+
});
|
|
1112
1180
|
return;
|
|
1113
1181
|
}
|
|
1114
1182
|
const obj = value;
|
|
@@ -1118,7 +1186,9 @@ export function deepFilterThinkingBlocks(payload, sessionId, getCachedSignatureF
|
|
|
1118
1186
|
if (Array.isArray(obj.messages)) {
|
|
1119
1187
|
obj.messages = filterMessagesThinkingBlocks(obj.messages, sessionId, getCachedSignatureFn, isClaudeModel);
|
|
1120
1188
|
}
|
|
1121
|
-
Object.keys(obj).forEach((key) =>
|
|
1189
|
+
Object.keys(obj).forEach((key) => {
|
|
1190
|
+
walk(obj[key]);
|
|
1191
|
+
});
|
|
1122
1192
|
};
|
|
1123
1193
|
walk(payload);
|
|
1124
1194
|
return payload;
|
|
@@ -1129,47 +1199,53 @@ export function deepFilterThinkingBlocks(payload, sessionId, getCachedSignatureF
|
|
|
1129
1199
|
* Claude responses through Antigravity may use candidates structure with Anthropic-style parts.
|
|
1130
1200
|
*/
|
|
1131
1201
|
function transformGeminiCandidate(candidate) {
|
|
1132
|
-
if (!candidate || typeof candidate !==
|
|
1202
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
1133
1203
|
return candidate;
|
|
1134
1204
|
}
|
|
1135
1205
|
const content = candidate.content;
|
|
1136
|
-
if (!content ||
|
|
1206
|
+
if (!content ||
|
|
1207
|
+
typeof content !== 'object' ||
|
|
1208
|
+
!Array.isArray(content.parts)) {
|
|
1137
1209
|
return candidate;
|
|
1138
1210
|
}
|
|
1139
1211
|
const thinkingTexts = [];
|
|
1140
1212
|
const transformedParts = content.parts.map((part) => {
|
|
1141
|
-
if (!part || typeof part !==
|
|
1213
|
+
if (!part || typeof part !== 'object') {
|
|
1142
1214
|
return part;
|
|
1143
1215
|
}
|
|
1144
1216
|
// Handle Gemini-style: thought: true
|
|
1145
1217
|
if (part.thought === true) {
|
|
1146
|
-
const thinkingText = typeof part.text ===
|
|
1218
|
+
const thinkingText = typeof part.text === 'string' ? part.text : '';
|
|
1147
1219
|
thinkingTexts.push(thinkingText);
|
|
1148
1220
|
// Clean object — NO spread to prevent thinking: <object> leaking into output
|
|
1149
1221
|
const transformed = {
|
|
1150
|
-
type:
|
|
1222
|
+
type: 'reasoning',
|
|
1151
1223
|
text: thinkingText,
|
|
1152
1224
|
thought: true,
|
|
1153
1225
|
};
|
|
1154
1226
|
const sig = part.thoughtSignature || part.signature;
|
|
1155
|
-
if (typeof sig ===
|
|
1227
|
+
if (typeof sig === 'string' && sig)
|
|
1156
1228
|
transformed.thoughtSignature = sig;
|
|
1157
1229
|
if (part.cache_control)
|
|
1158
1230
|
transformed.cache_control = part.cache_control;
|
|
1159
1231
|
return transformed;
|
|
1160
1232
|
}
|
|
1161
1233
|
// Handle Anthropic-style in candidates: type: "thinking"
|
|
1162
|
-
if (part.type ===
|
|
1163
|
-
const thinkingText = typeof part.thinking ===
|
|
1234
|
+
if (part.type === 'thinking') {
|
|
1235
|
+
const thinkingText = typeof part.thinking === 'string'
|
|
1236
|
+
? part.thinking
|
|
1237
|
+
: typeof part.text === 'string'
|
|
1238
|
+
? part.text
|
|
1239
|
+
: '';
|
|
1164
1240
|
thinkingTexts.push(thinkingText);
|
|
1165
1241
|
// Clean object — NO spread to prevent thinking: <object> leaking into output
|
|
1166
1242
|
const transformed = {
|
|
1167
|
-
type:
|
|
1243
|
+
type: 'reasoning',
|
|
1168
1244
|
text: thinkingText,
|
|
1169
1245
|
thought: true,
|
|
1170
1246
|
};
|
|
1171
1247
|
const sig = part.thoughtSignature || part.signature;
|
|
1172
|
-
if (typeof sig ===
|
|
1248
|
+
if (typeof sig === 'string' && sig)
|
|
1173
1249
|
transformed.thoughtSignature = sig;
|
|
1174
1250
|
if (part.cache_control)
|
|
1175
1251
|
transformed.cache_control = part.cache_control;
|
|
@@ -1206,7 +1282,9 @@ function transformGeminiCandidate(candidate) {
|
|
|
1206
1282
|
return {
|
|
1207
1283
|
...candidate,
|
|
1208
1284
|
content: { ...content, parts: transformedParts },
|
|
1209
|
-
...(thinkingTexts.length > 0
|
|
1285
|
+
...(thinkingTexts.length > 0
|
|
1286
|
+
? { reasoning_content: thinkingTexts.join('\n\n') }
|
|
1287
|
+
: {}),
|
|
1210
1288
|
};
|
|
1211
1289
|
}
|
|
1212
1290
|
/**
|
|
@@ -1215,7 +1293,7 @@ function transformGeminiCandidate(candidate) {
|
|
|
1215
1293
|
* Also extracts reasoning_content for Anthropic-style responses.
|
|
1216
1294
|
*/
|
|
1217
1295
|
export function transformThinkingParts(response) {
|
|
1218
|
-
if (!response || typeof response !==
|
|
1296
|
+
if (!response || typeof response !== 'object') {
|
|
1219
1297
|
return response;
|
|
1220
1298
|
}
|
|
1221
1299
|
const resp = response;
|
|
@@ -1225,17 +1303,23 @@ export function transformThinkingParts(response) {
|
|
|
1225
1303
|
if (Array.isArray(resp.content)) {
|
|
1226
1304
|
const transformedContent = [];
|
|
1227
1305
|
for (const block of resp.content) {
|
|
1228
|
-
if (block &&
|
|
1229
|
-
|
|
1306
|
+
if (block &&
|
|
1307
|
+
typeof block === 'object' &&
|
|
1308
|
+
block.type === 'thinking') {
|
|
1309
|
+
const thinkingText = typeof block.thinking === 'string'
|
|
1310
|
+
? block.thinking
|
|
1311
|
+
: typeof block.text === 'string'
|
|
1312
|
+
? block.text
|
|
1313
|
+
: '';
|
|
1230
1314
|
reasoningTexts.push(thinkingText);
|
|
1231
1315
|
// Clean object — NO spread to prevent thinking: <object> leaking into output
|
|
1232
1316
|
const transformed = {
|
|
1233
|
-
type:
|
|
1317
|
+
type: 'reasoning',
|
|
1234
1318
|
text: thinkingText,
|
|
1235
1319
|
thought: true,
|
|
1236
1320
|
};
|
|
1237
1321
|
const sig = block.thoughtSignature || block.signature;
|
|
1238
|
-
if (typeof sig ===
|
|
1322
|
+
if (typeof sig === 'string' && sig)
|
|
1239
1323
|
transformed.thoughtSignature = sig;
|
|
1240
1324
|
if (block.cache_control)
|
|
1241
1325
|
transformed.cache_control = block.cache_control;
|
|
@@ -1253,7 +1337,7 @@ export function transformThinkingParts(response) {
|
|
|
1253
1337
|
}
|
|
1254
1338
|
// Add reasoning_content if we found any thinking blocks (for Anthropic-style)
|
|
1255
1339
|
if (reasoningTexts.length > 0 && !result.reasoning_content) {
|
|
1256
|
-
result.reasoning_content = reasoningTexts.join(
|
|
1340
|
+
result.reasoning_content = reasoningTexts.join('\n\n');
|
|
1257
1341
|
}
|
|
1258
1342
|
return result;
|
|
1259
1343
|
}
|
|
@@ -1261,17 +1345,22 @@ export function transformThinkingParts(response) {
|
|
|
1261
1345
|
* Ensures thinkingConfig is valid: includeThoughts only allowed when budget > 0.
|
|
1262
1346
|
*/
|
|
1263
1347
|
export function normalizeThinkingConfig(config) {
|
|
1264
|
-
if (!config || typeof config !==
|
|
1348
|
+
if (!config || typeof config !== 'object') {
|
|
1265
1349
|
return undefined;
|
|
1266
1350
|
}
|
|
1267
1351
|
const record = config;
|
|
1268
1352
|
const budgetRaw = record.thinkingBudget ?? record.thinking_budget;
|
|
1269
1353
|
const includeRaw = record.includeThoughts ?? record.include_thoughts;
|
|
1270
|
-
const thinkingBudget = typeof budgetRaw ===
|
|
1271
|
-
|
|
1354
|
+
const thinkingBudget = typeof budgetRaw === 'number' && Number.isFinite(budgetRaw)
|
|
1355
|
+
? budgetRaw
|
|
1356
|
+
: undefined;
|
|
1357
|
+
const includeThoughts = typeof includeRaw === 'boolean' ? includeRaw : undefined;
|
|
1272
1358
|
const enableThinking = thinkingBudget !== undefined && thinkingBudget > 0;
|
|
1273
|
-
const finalInclude = enableThinking ? includeThoughts ?? false : false;
|
|
1274
|
-
if (!enableThinking &&
|
|
1359
|
+
const finalInclude = enableThinking ? (includeThoughts ?? false) : false;
|
|
1360
|
+
if (!enableThinking &&
|
|
1361
|
+
finalInclude === false &&
|
|
1362
|
+
thinkingBudget === undefined &&
|
|
1363
|
+
includeThoughts === undefined) {
|
|
1275
1364
|
return undefined;
|
|
1276
1365
|
}
|
|
1277
1366
|
const normalized = {};
|
|
@@ -1290,13 +1379,13 @@ export function parseAntigravityApiBody(rawText) {
|
|
|
1290
1379
|
try {
|
|
1291
1380
|
const parsed = JSON.parse(rawText);
|
|
1292
1381
|
if (Array.isArray(parsed)) {
|
|
1293
|
-
const firstObject = parsed.find((item) => typeof item ===
|
|
1294
|
-
if (firstObject && typeof firstObject ===
|
|
1382
|
+
const firstObject = parsed.find((item) => typeof item === 'object' && item !== null);
|
|
1383
|
+
if (firstObject && typeof firstObject === 'object') {
|
|
1295
1384
|
return firstObject;
|
|
1296
1385
|
}
|
|
1297
1386
|
return null;
|
|
1298
1387
|
}
|
|
1299
|
-
if (parsed && typeof parsed ===
|
|
1388
|
+
if (parsed && typeof parsed === 'object') {
|
|
1300
1389
|
return parsed;
|
|
1301
1390
|
}
|
|
1302
1391
|
return null;
|
|
@@ -1309,14 +1398,14 @@ export function parseAntigravityApiBody(rawText) {
|
|
|
1309
1398
|
* Extracts usageMetadata from a response object, guarding types.
|
|
1310
1399
|
*/
|
|
1311
1400
|
export function extractUsageMetadata(body) {
|
|
1312
|
-
const usage = (body.response && typeof body.response ===
|
|
1401
|
+
const usage = (body.response && typeof body.response === 'object'
|
|
1313
1402
|
? body.response.usageMetadata
|
|
1314
1403
|
: undefined);
|
|
1315
|
-
if (!usage || typeof usage !==
|
|
1404
|
+
if (!usage || typeof usage !== 'object') {
|
|
1316
1405
|
return null;
|
|
1317
1406
|
}
|
|
1318
1407
|
const asRecord = usage;
|
|
1319
|
-
const toNumber = (value) => typeof value ===
|
|
1408
|
+
const toNumber = (value) => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
1320
1409
|
return {
|
|
1321
1410
|
totalTokenCount: toNumber(asRecord.totalTokenCount),
|
|
1322
1411
|
promptTokenCount: toNumber(asRecord.promptTokenCount),
|
|
@@ -1329,9 +1418,9 @@ export function extractUsageMetadata(body) {
|
|
|
1329
1418
|
* Walks SSE lines to find a usage-bearing response chunk.
|
|
1330
1419
|
*/
|
|
1331
1420
|
export function extractUsageFromSsePayload(payload) {
|
|
1332
|
-
const lines = payload.split(
|
|
1421
|
+
const lines = payload.split('\n');
|
|
1333
1422
|
for (const line of lines) {
|
|
1334
|
-
if (!line.startsWith(
|
|
1423
|
+
if (!line.startsWith('data:')) {
|
|
1335
1424
|
continue;
|
|
1336
1425
|
}
|
|
1337
1426
|
const jsonText = line.slice(5).trim();
|
|
@@ -1340,16 +1429,16 @@ export function extractUsageFromSsePayload(payload) {
|
|
|
1340
1429
|
}
|
|
1341
1430
|
try {
|
|
1342
1431
|
const parsed = JSON.parse(jsonText);
|
|
1343
|
-
if (parsed && typeof parsed ===
|
|
1344
|
-
const usage = extractUsageMetadata({
|
|
1432
|
+
if (parsed && typeof parsed === 'object') {
|
|
1433
|
+
const usage = extractUsageMetadata({
|
|
1434
|
+
response: parsed.response,
|
|
1435
|
+
});
|
|
1345
1436
|
if (usage) {
|
|
1346
1437
|
return usage;
|
|
1347
1438
|
}
|
|
1348
1439
|
}
|
|
1349
1440
|
}
|
|
1350
|
-
catch {
|
|
1351
|
-
continue;
|
|
1352
|
-
}
|
|
1441
|
+
catch { }
|
|
1353
1442
|
}
|
|
1354
1443
|
return null;
|
|
1355
1444
|
}
|
|
@@ -1361,10 +1450,10 @@ export function rewriteAntigravityPreviewAccessError(body, status, requestedMode
|
|
|
1361
1450
|
return null;
|
|
1362
1451
|
}
|
|
1363
1452
|
const error = body.error ?? {};
|
|
1364
|
-
const trimmedMessage = typeof error.message ===
|
|
1453
|
+
const trimmedMessage = typeof error.message === 'string' ? error.message.trim() : '';
|
|
1365
1454
|
const messagePrefix = trimmedMessage.length > 0
|
|
1366
1455
|
? trimmedMessage
|
|
1367
|
-
:
|
|
1456
|
+
: 'Antigravity preview features are not enabled for this account.';
|
|
1368
1457
|
const enhancedMessage = `${messagePrefix} Request preview access at ${ANTIGRAVITY_PREVIEW_LINK} before using this model.`;
|
|
1369
1458
|
return {
|
|
1370
1459
|
...body,
|
|
@@ -1381,7 +1470,7 @@ function needsPreviewAccessOverride(status, body, requestedModel) {
|
|
|
1381
1470
|
if (isAntigravityModel(requestedModel)) {
|
|
1382
1471
|
return true;
|
|
1383
1472
|
}
|
|
1384
|
-
const errorMessage = typeof body.error?.message ===
|
|
1473
|
+
const errorMessage = typeof body.error?.message === 'string' ? body.error.message : '';
|
|
1385
1474
|
return isAntigravityModel(errorMessage);
|
|
1386
1475
|
}
|
|
1387
1476
|
function isAntigravityModel(target) {
|
|
@@ -1389,7 +1478,9 @@ function isAntigravityModel(target) {
|
|
|
1389
1478
|
return false;
|
|
1390
1479
|
}
|
|
1391
1480
|
// Check for Antigravity models instead of Gemini 3
|
|
1392
|
-
return /antigravity/i.test(target) ||
|
|
1481
|
+
return (/antigravity/i.test(target) ||
|
|
1482
|
+
/opus/i.test(target) ||
|
|
1483
|
+
/claude/i.test(target));
|
|
1393
1484
|
}
|
|
1394
1485
|
// ============================================================================
|
|
1395
1486
|
// EMPTY RESPONSE DETECTION (Ported from LLM-API-Key-Proxy)
|
|
@@ -1406,7 +1497,7 @@ function isAntigravityModel(target) {
|
|
|
1406
1497
|
* @returns true if the response is empty
|
|
1407
1498
|
*/
|
|
1408
1499
|
export function isEmptyResponseBody(text) {
|
|
1409
|
-
if (!text
|
|
1500
|
+
if (!text?.trim()) {
|
|
1410
1501
|
return true;
|
|
1411
1502
|
}
|
|
1412
1503
|
try {
|
|
@@ -1423,7 +1514,7 @@ export function isEmptyResponseBody(text) {
|
|
|
1423
1514
|
}
|
|
1424
1515
|
// Check for empty parts in content
|
|
1425
1516
|
const content = firstCandidate.content;
|
|
1426
|
-
if (!content || typeof content !==
|
|
1517
|
+
if (!content || typeof content !== 'object') {
|
|
1427
1518
|
return true;
|
|
1428
1519
|
}
|
|
1429
1520
|
const parts = content.parts;
|
|
@@ -1432,13 +1523,13 @@ export function isEmptyResponseBody(text) {
|
|
|
1432
1523
|
}
|
|
1433
1524
|
// Check if all parts are empty (no text, no functionCall)
|
|
1434
1525
|
const hasContent = parts.some((part) => {
|
|
1435
|
-
if (!part || typeof part !==
|
|
1526
|
+
if (!part || typeof part !== 'object')
|
|
1436
1527
|
return false;
|
|
1437
|
-
if (typeof part.text ===
|
|
1528
|
+
if (typeof part.text === 'string' && part.text.length > 0)
|
|
1438
1529
|
return true;
|
|
1439
1530
|
if (part.functionCall)
|
|
1440
1531
|
return true;
|
|
1441
|
-
if (part.thought === true && typeof part.text ===
|
|
1532
|
+
if (part.thought === true && typeof part.text === 'string')
|
|
1442
1533
|
return true;
|
|
1443
1534
|
return false;
|
|
1444
1535
|
});
|
|
@@ -1461,14 +1552,16 @@ export function isEmptyResponseBody(text) {
|
|
|
1461
1552
|
return true;
|
|
1462
1553
|
}
|
|
1463
1554
|
// Check if message has content or tool_calls
|
|
1464
|
-
if (!message.content &&
|
|
1555
|
+
if (!message.content &&
|
|
1556
|
+
!message.tool_calls &&
|
|
1557
|
+
!message.reasoning_content) {
|
|
1465
1558
|
return true;
|
|
1466
1559
|
}
|
|
1467
1560
|
}
|
|
1468
1561
|
// Check response wrapper (Antigravity envelope)
|
|
1469
1562
|
if (parsed.response !== undefined) {
|
|
1470
1563
|
const response = parsed.response;
|
|
1471
|
-
if (!response || typeof response !==
|
|
1564
|
+
if (!response || typeof response !== 'object') {
|
|
1472
1565
|
return true;
|
|
1473
1566
|
}
|
|
1474
1567
|
return isEmptyResponseBody(JSON.stringify(response));
|
|
@@ -1482,7 +1575,7 @@ export function isEmptyResponseBody(text) {
|
|
|
1482
1575
|
}
|
|
1483
1576
|
export function createStreamingChunkCounter() {
|
|
1484
1577
|
let count = 0;
|
|
1485
|
-
|
|
1578
|
+
const hasRealContent = false;
|
|
1486
1579
|
return {
|
|
1487
1580
|
increment: () => {
|
|
1488
1581
|
count++;
|
|
@@ -1498,11 +1591,11 @@ export function createStreamingChunkCounter() {
|
|
|
1498
1591
|
* @returns true if the line contains content worth counting
|
|
1499
1592
|
*/
|
|
1500
1593
|
export function isMeaningfulSseLine(line) {
|
|
1501
|
-
if (!line.startsWith(
|
|
1594
|
+
if (!line.startsWith('data: ')) {
|
|
1502
1595
|
return false;
|
|
1503
1596
|
}
|
|
1504
1597
|
const data = line.slice(6).trim();
|
|
1505
|
-
if (data ===
|
|
1598
|
+
if (data === '[DONE]') {
|
|
1506
1599
|
return false;
|
|
1507
1600
|
}
|
|
1508
1601
|
if (!data) {
|
|
@@ -1516,7 +1609,7 @@ export function isMeaningfulSseLine(line) {
|
|
|
1516
1609
|
const parts = candidate?.content?.parts;
|
|
1517
1610
|
if (Array.isArray(parts) && parts.length > 0) {
|
|
1518
1611
|
for (const part of parts) {
|
|
1519
|
-
if (typeof part?.text ===
|
|
1612
|
+
if (typeof part?.text === 'string' && part.text.length > 0)
|
|
1520
1613
|
return true;
|
|
1521
1614
|
if (part?.functionCall)
|
|
1522
1615
|
return true;
|
|
@@ -1557,29 +1650,29 @@ export function isMeaningfulSseLine(line) {
|
|
|
1557
1650
|
*/
|
|
1558
1651
|
// Keys whose string values should NOT be parsed as JSON - they contain literal text content
|
|
1559
1652
|
const SKIP_PARSE_KEYS = new Set([
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1653
|
+
'oldString',
|
|
1654
|
+
'newString',
|
|
1655
|
+
'content',
|
|
1656
|
+
'filePath',
|
|
1657
|
+
'path',
|
|
1658
|
+
'text',
|
|
1659
|
+
'code',
|
|
1660
|
+
'source',
|
|
1661
|
+
'data',
|
|
1662
|
+
'body',
|
|
1663
|
+
'message',
|
|
1664
|
+
'prompt',
|
|
1665
|
+
'input',
|
|
1666
|
+
'output',
|
|
1667
|
+
'result',
|
|
1668
|
+
'value',
|
|
1669
|
+
'query',
|
|
1670
|
+
'pattern',
|
|
1671
|
+
'replacement',
|
|
1672
|
+
'template',
|
|
1673
|
+
'script',
|
|
1674
|
+
'command',
|
|
1675
|
+
'snippet',
|
|
1583
1676
|
]);
|
|
1584
1677
|
export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS, currentKey) {
|
|
1585
1678
|
if (obj === null || obj === undefined) {
|
|
@@ -1588,14 +1681,14 @@ export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS
|
|
|
1588
1681
|
if (Array.isArray(obj)) {
|
|
1589
1682
|
return obj.map((item) => recursivelyParseJsonStrings(item, skipParseKeys));
|
|
1590
1683
|
}
|
|
1591
|
-
if (typeof obj ===
|
|
1684
|
+
if (typeof obj === 'object') {
|
|
1592
1685
|
const result = {};
|
|
1593
1686
|
for (const [key, value] of Object.entries(obj)) {
|
|
1594
1687
|
result[key] = recursivelyParseJsonStrings(value, skipParseKeys, key);
|
|
1595
1688
|
}
|
|
1596
1689
|
return result;
|
|
1597
1690
|
}
|
|
1598
|
-
if (typeof obj !==
|
|
1691
|
+
if (typeof obj !== 'string') {
|
|
1599
1692
|
return obj;
|
|
1600
1693
|
}
|
|
1601
1694
|
if (currentKey && skipParseKeys.has(currentKey)) {
|
|
@@ -1604,8 +1697,8 @@ export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS
|
|
|
1604
1697
|
const stripped = obj.trim();
|
|
1605
1698
|
// Check if string contains control character escape sequences
|
|
1606
1699
|
// that need unescaping (\\n, \\t but NOT \\" or \\\\)
|
|
1607
|
-
const hasControlCharEscapes = obj.includes(
|
|
1608
|
-
const hasIntentionalEscapes = obj.includes('\\"') || obj.includes(
|
|
1700
|
+
const hasControlCharEscapes = obj.includes('\\n') || obj.includes('\\t');
|
|
1701
|
+
const hasIntentionalEscapes = obj.includes('\\"') || obj.includes('\\\\');
|
|
1609
1702
|
if (hasControlCharEscapes && !hasIntentionalEscapes) {
|
|
1610
1703
|
try {
|
|
1611
1704
|
// Use JSON.parse with quotes to unescape the string
|
|
@@ -1616,10 +1709,10 @@ export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS
|
|
|
1616
1709
|
}
|
|
1617
1710
|
}
|
|
1618
1711
|
// Check if it looks like JSON (starts with { or [)
|
|
1619
|
-
if (stripped && (stripped[0] ===
|
|
1712
|
+
if (stripped && (stripped[0] === '{' || stripped[0] === '[')) {
|
|
1620
1713
|
// Try standard parsing first
|
|
1621
|
-
if ((stripped.startsWith(
|
|
1622
|
-
(stripped.startsWith(
|
|
1714
|
+
if ((stripped.startsWith('{') && stripped.endsWith('}')) ||
|
|
1715
|
+
(stripped.startsWith('[') && stripped.endsWith(']'))) {
|
|
1623
1716
|
try {
|
|
1624
1717
|
const parsed = JSON.parse(obj);
|
|
1625
1718
|
return recursivelyParseJsonStrings(parsed);
|
|
@@ -1629,13 +1722,13 @@ export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS
|
|
|
1629
1722
|
}
|
|
1630
1723
|
}
|
|
1631
1724
|
// Handle malformed JSON: array that doesn't end with ]
|
|
1632
|
-
if (stripped.startsWith(
|
|
1725
|
+
if (stripped.startsWith('[') && !stripped.endsWith(']')) {
|
|
1633
1726
|
try {
|
|
1634
|
-
const lastBracket = stripped.lastIndexOf(
|
|
1727
|
+
const lastBracket = stripped.lastIndexOf(']');
|
|
1635
1728
|
if (lastBracket > 0) {
|
|
1636
1729
|
const cleaned = stripped.slice(0, lastBracket + 1);
|
|
1637
1730
|
const parsed = JSON.parse(cleaned);
|
|
1638
|
-
log.debug(
|
|
1731
|
+
log.debug('Auto-corrected malformed JSON array', {
|
|
1639
1732
|
truncatedChars: stripped.length - cleaned.length,
|
|
1640
1733
|
});
|
|
1641
1734
|
return recursivelyParseJsonStrings(parsed);
|
|
@@ -1646,13 +1739,13 @@ export function recursivelyParseJsonStrings(obj, skipParseKeys = SKIP_PARSE_KEYS
|
|
|
1646
1739
|
}
|
|
1647
1740
|
}
|
|
1648
1741
|
// Handle malformed JSON: object that doesn't end with }
|
|
1649
|
-
if (stripped.startsWith(
|
|
1742
|
+
if (stripped.startsWith('{') && !stripped.endsWith('}')) {
|
|
1650
1743
|
try {
|
|
1651
|
-
const lastBrace = stripped.lastIndexOf(
|
|
1744
|
+
const lastBrace = stripped.lastIndexOf('}');
|
|
1652
1745
|
if (lastBrace > 0) {
|
|
1653
1746
|
const cleaned = stripped.slice(0, lastBrace + 1);
|
|
1654
1747
|
const parsed = JSON.parse(cleaned);
|
|
1655
|
-
log.debug(
|
|
1748
|
+
log.debug('Auto-corrected malformed JSON object', {
|
|
1656
1749
|
truncatedChars: stripped.length - cleaned.length,
|
|
1657
1750
|
});
|
|
1658
1751
|
return recursivelyParseJsonStrings(parsed);
|
|
@@ -1701,7 +1794,7 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1701
1794
|
if (responseParts.length > 0) {
|
|
1702
1795
|
// Collect responses by ID (skip duplicates)
|
|
1703
1796
|
for (const resp of responseParts) {
|
|
1704
|
-
const respId = resp.functionResponse?.id ||
|
|
1797
|
+
const respId = resp.functionResponse?.id || '';
|
|
1705
1798
|
if (respId && !collectedResponses.has(respId)) {
|
|
1706
1799
|
collectedResponses.set(respId, resp);
|
|
1707
1800
|
}
|
|
@@ -1709,30 +1802,29 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1709
1802
|
// Try to satisfy the most recent pending group
|
|
1710
1803
|
for (let i = pendingGroups.length - 1; i >= 0; i--) {
|
|
1711
1804
|
const group = pendingGroups[i];
|
|
1712
|
-
if (group.ids.every(id => collectedResponses.has(id))) {
|
|
1805
|
+
if (group.ids.every((id) => collectedResponses.has(id))) {
|
|
1713
1806
|
// All IDs found - build the response group
|
|
1714
|
-
const groupResponses = group.ids.map(id => {
|
|
1807
|
+
const groupResponses = group.ids.map((id) => {
|
|
1715
1808
|
const resp = collectedResponses.get(id);
|
|
1716
1809
|
collectedResponses.delete(id);
|
|
1717
1810
|
return resp;
|
|
1718
1811
|
});
|
|
1719
|
-
newContents.push({ parts: groupResponses, role:
|
|
1812
|
+
newContents.push({ parts: groupResponses, role: 'user' });
|
|
1720
1813
|
pendingGroups.splice(i, 1);
|
|
1721
1814
|
break; // Only satisfy one group at a time
|
|
1722
1815
|
}
|
|
1723
1816
|
}
|
|
1724
1817
|
continue; // Don't add the original response message
|
|
1725
1818
|
}
|
|
1726
|
-
if (role ===
|
|
1819
|
+
if (role === 'model') {
|
|
1727
1820
|
// Check for function calls in this model message
|
|
1728
1821
|
const funcCalls = parts.filter((p) => p?.functionCall);
|
|
1729
1822
|
newContents.push(content);
|
|
1730
1823
|
if (funcCalls.length > 0) {
|
|
1731
1824
|
const callIds = funcCalls
|
|
1732
|
-
.map((fc) => fc.functionCall?.id ||
|
|
1825
|
+
.map((fc) => fc.functionCall?.id || '')
|
|
1733
1826
|
.filter(Boolean);
|
|
1734
|
-
const funcNames = funcCalls
|
|
1735
|
-
.map((fc) => fc.functionCall?.name || "");
|
|
1827
|
+
const funcNames = funcCalls.map((fc) => fc.functionCall?.name || '');
|
|
1736
1828
|
if (callIds.length > 0) {
|
|
1737
1829
|
pendingGroups.push({
|
|
1738
1830
|
ids: callIds,
|
|
@@ -1753,7 +1845,7 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1753
1845
|
const groupResponses = [];
|
|
1754
1846
|
for (let i = 0; i < group.ids.length; i++) {
|
|
1755
1847
|
const expectedId = group.ids[i];
|
|
1756
|
-
const expectedName = group.funcNames[i] ||
|
|
1848
|
+
const expectedName = group.funcNames[i] || '';
|
|
1757
1849
|
if (collectedResponses.has(expectedId)) {
|
|
1758
1850
|
// Direct ID match - ideal case
|
|
1759
1851
|
groupResponses.push(collectedResponses.get(expectedId));
|
|
@@ -1764,7 +1856,7 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1764
1856
|
let matchedId = null;
|
|
1765
1857
|
// Pass 1: Match by function name
|
|
1766
1858
|
for (const [orphanId, orphanResp] of collectedResponses) {
|
|
1767
|
-
const orphanName = orphanResp.functionResponse?.name ||
|
|
1859
|
+
const orphanName = orphanResp.functionResponse?.name || '';
|
|
1768
1860
|
if (orphanName === expectedName) {
|
|
1769
1861
|
matchedId = orphanId;
|
|
1770
1862
|
break;
|
|
@@ -1773,7 +1865,7 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1773
1865
|
// Pass 2: Match "unknown_function" orphans
|
|
1774
1866
|
if (!matchedId) {
|
|
1775
1867
|
for (const [orphanId, orphanResp] of collectedResponses) {
|
|
1776
|
-
if (orphanResp.functionResponse?.name ===
|
|
1868
|
+
if (orphanResp.functionResponse?.name === 'unknown_function') {
|
|
1777
1869
|
matchedId = orphanId;
|
|
1778
1870
|
break;
|
|
1779
1871
|
}
|
|
@@ -1788,10 +1880,11 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1788
1880
|
collectedResponses.delete(matchedId);
|
|
1789
1881
|
// Fix the ID and name to match expected
|
|
1790
1882
|
orphanResp.functionResponse.id = expectedId;
|
|
1791
|
-
if (orphanResp.functionResponse.name ===
|
|
1883
|
+
if (orphanResp.functionResponse.name === 'unknown_function' &&
|
|
1884
|
+
expectedName) {
|
|
1792
1885
|
orphanResp.functionResponse.name = expectedName;
|
|
1793
1886
|
}
|
|
1794
|
-
log.debug(
|
|
1887
|
+
log.debug('Auto-repaired tool ID mismatch', {
|
|
1795
1888
|
mappedFrom: matchedId,
|
|
1796
1889
|
mappedTo: expectedId,
|
|
1797
1890
|
functionName: expectedName,
|
|
@@ -1803,18 +1896,18 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1803
1896
|
// No responses available - create placeholder
|
|
1804
1897
|
const placeholder = {
|
|
1805
1898
|
functionResponse: {
|
|
1806
|
-
name: expectedName ||
|
|
1899
|
+
name: expectedName || 'unknown_function',
|
|
1807
1900
|
response: {
|
|
1808
1901
|
result: {
|
|
1809
|
-
error:
|
|
1810
|
-
|
|
1902
|
+
error: 'Tool response was lost during context processing. ' +
|
|
1903
|
+
'This is a recovered placeholder.',
|
|
1811
1904
|
recovered: true,
|
|
1812
1905
|
},
|
|
1813
1906
|
},
|
|
1814
1907
|
id: expectedId,
|
|
1815
1908
|
},
|
|
1816
1909
|
};
|
|
1817
|
-
log.debug(
|
|
1910
|
+
log.debug('Created placeholder response for missing tool', {
|
|
1818
1911
|
id: expectedId,
|
|
1819
1912
|
name: expectedName,
|
|
1820
1913
|
});
|
|
@@ -1825,7 +1918,7 @@ export function fixToolResponseGrouping(contents) {
|
|
|
1825
1918
|
// Insert at correct position (after the model message that made the calls)
|
|
1826
1919
|
newContents.splice(group.insertAfterIdx + 1, 0, {
|
|
1827
1920
|
parts: groupResponses,
|
|
1828
|
-
role:
|
|
1921
|
+
role: 'user',
|
|
1829
1922
|
});
|
|
1830
1923
|
}
|
|
1831
1924
|
}
|
|
@@ -1853,8 +1946,8 @@ export function detectToolIdMismatches(contents) {
|
|
|
1853
1946
|
}
|
|
1854
1947
|
const expectedSet = new Set(expectedIds);
|
|
1855
1948
|
const foundSet = new Set(foundIds);
|
|
1856
|
-
const missingIds = expectedIds.filter(id => !foundSet.has(id));
|
|
1857
|
-
const orphanIds = foundIds.filter(id => !expectedSet.has(id));
|
|
1949
|
+
const missingIds = expectedIds.filter((id) => !foundSet.has(id));
|
|
1950
|
+
const orphanIds = foundIds.filter((id) => !expectedSet.has(id));
|
|
1858
1951
|
return {
|
|
1859
1952
|
hasMismatches: missingIds.length > 0 || orphanIds.length > 0,
|
|
1860
1953
|
expectedIds,
|
|
@@ -1876,10 +1969,10 @@ export function findOrphanedToolUseIds(messages) {
|
|
|
1876
1969
|
for (const msg of messages) {
|
|
1877
1970
|
if (Array.isArray(msg.content)) {
|
|
1878
1971
|
for (const block of msg.content) {
|
|
1879
|
-
if (block.type ===
|
|
1972
|
+
if (block.type === 'tool_use' && block.id) {
|
|
1880
1973
|
toolUseIds.add(block.id);
|
|
1881
1974
|
}
|
|
1882
|
-
if (block.type ===
|
|
1975
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
1883
1976
|
toolResultIds.add(block.tool_use_id);
|
|
1884
1977
|
}
|
|
1885
1978
|
}
|
|
@@ -1906,10 +1999,13 @@ export function fixClaudeToolPairing(messages) {
|
|
|
1906
1999
|
const toolUseMap = new Map();
|
|
1907
2000
|
for (let i = 0; i < messages.length; i++) {
|
|
1908
2001
|
const msg = messages[i];
|
|
1909
|
-
if (msg.role ===
|
|
2002
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1910
2003
|
for (const block of msg.content) {
|
|
1911
|
-
if (block.type ===
|
|
1912
|
-
toolUseMap.set(block.id, {
|
|
2004
|
+
if (block.type === 'tool_use' && block.id) {
|
|
2005
|
+
toolUseMap.set(block.id, {
|
|
2006
|
+
name: block.name || `tool-${toolUseMap.size}`,
|
|
2007
|
+
msgIndex: i,
|
|
2008
|
+
});
|
|
1913
2009
|
}
|
|
1914
2010
|
}
|
|
1915
2011
|
}
|
|
@@ -1917,9 +2013,9 @@ export function fixClaudeToolPairing(messages) {
|
|
|
1917
2013
|
// 2. Collect all tool_result IDs from user messages
|
|
1918
2014
|
const toolResultIds = new Set();
|
|
1919
2015
|
for (const msg of messages) {
|
|
1920
|
-
if (msg.role ===
|
|
2016
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
1921
2017
|
for (const block of msg.content) {
|
|
1922
|
-
if (block.type ===
|
|
2018
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
1923
2019
|
toolResultIds.add(block.tool_use_id);
|
|
1924
2020
|
}
|
|
1925
2021
|
}
|
|
@@ -1950,10 +2046,10 @@ export function fixClaudeToolPairing(messages) {
|
|
|
1950
2046
|
if (orphansForMsg && orphansForMsg.length > 0) {
|
|
1951
2047
|
// Check if next message is user with tool_result - if so, merge into it
|
|
1952
2048
|
const nextMsg = messages[i + 1];
|
|
1953
|
-
if (nextMsg?.role ===
|
|
2049
|
+
if (nextMsg?.role === 'user' && Array.isArray(nextMsg.content)) {
|
|
1954
2050
|
// Will be handled when we push nextMsg - add to its content
|
|
1955
2051
|
const placeholders = orphansForMsg.map((o) => ({
|
|
1956
|
-
type:
|
|
2052
|
+
type: 'tool_result',
|
|
1957
2053
|
tool_use_id: o.id,
|
|
1958
2054
|
content: `[Tool "${o.name}" execution was cancelled or failed]`,
|
|
1959
2055
|
is_error: true,
|
|
@@ -1964,9 +2060,9 @@ export function fixClaudeToolPairing(messages) {
|
|
|
1964
2060
|
else {
|
|
1965
2061
|
// Inject new user message with placeholder tool_results
|
|
1966
2062
|
result.push({
|
|
1967
|
-
role:
|
|
2063
|
+
role: 'user',
|
|
1968
2064
|
content: orphansForMsg.map((o) => ({
|
|
1969
|
-
type:
|
|
2065
|
+
type: 'tool_result',
|
|
1970
2066
|
tool_use_id: o.id,
|
|
1971
2067
|
content: `[Tool "${o.name}" execution was cancelled or failed]`,
|
|
1972
2068
|
is_error: true,
|
|
@@ -1984,17 +2080,19 @@ export function fixClaudeToolPairing(messages) {
|
|
|
1984
2080
|
function removeOrphanedToolUse(messages, orphanIds) {
|
|
1985
2081
|
return messages
|
|
1986
2082
|
.map((msg) => {
|
|
1987
|
-
if (msg.role ===
|
|
2083
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1988
2084
|
return {
|
|
1989
2085
|
...msg,
|
|
1990
|
-
content: msg.content.filter((block) => block.type !==
|
|
2086
|
+
content: msg.content.filter((block) => block.type !== 'tool_use' || !orphanIds.has(block.id)),
|
|
1991
2087
|
};
|
|
1992
2088
|
}
|
|
1993
2089
|
return msg;
|
|
1994
2090
|
})
|
|
1995
2091
|
.filter((msg) =>
|
|
1996
2092
|
// Remove empty assistant messages
|
|
1997
|
-
!(msg.role ===
|
|
2093
|
+
!(msg.role === 'assistant' &&
|
|
2094
|
+
Array.isArray(msg.content) &&
|
|
2095
|
+
msg.content.length === 0));
|
|
1998
2096
|
}
|
|
1999
2097
|
/**
|
|
2000
2098
|
* Validate and fix tool pairing with fallback nuclear option.
|
|
@@ -2005,7 +2103,7 @@ export function validateAndFixClaudeToolPairing(messages) {
|
|
|
2005
2103
|
return messages;
|
|
2006
2104
|
}
|
|
2007
2105
|
// First: Try gentle fix (inject placeholder tool_results)
|
|
2008
|
-
|
|
2106
|
+
const fixed = fixClaudeToolPairing(messages);
|
|
2009
2107
|
// Second: Validate - find any remaining orphans
|
|
2010
2108
|
const orphanIds = findOrphanedToolUseIds(fixed);
|
|
2011
2109
|
if (orphanIds.size === 0) {
|
|
@@ -2013,7 +2111,7 @@ export function validateAndFixClaudeToolPairing(messages) {
|
|
|
2013
2111
|
}
|
|
2014
2112
|
// Third: Nuclear option - remove orphaned tool_use entirely
|
|
2015
2113
|
// This should rarely happen, but provides defense in depth
|
|
2016
|
-
console.warn(
|
|
2114
|
+
console.warn('[antigravity] fixClaudeToolPairing left orphans, applying nuclear option', {
|
|
2017
2115
|
orphanIds: [...orphanIds],
|
|
2018
2116
|
});
|
|
2019
2117
|
return removeOrphanedToolUse(fixed, orphanIds);
|
|
@@ -2026,12 +2124,12 @@ export function validateAndFixClaudeToolPairing(messages) {
|
|
|
2026
2124
|
* Port of LLM-API-Key-Proxy's _format_type_hint()
|
|
2027
2125
|
*/
|
|
2028
2126
|
function formatTypeHint(propData, depth = 0) {
|
|
2029
|
-
const type = propData.type ??
|
|
2127
|
+
const type = propData.type ?? 'unknown';
|
|
2030
2128
|
// Handle enum values
|
|
2031
2129
|
if (propData.enum && Array.isArray(propData.enum)) {
|
|
2032
2130
|
const enumVals = propData.enum;
|
|
2033
2131
|
if (enumVals.length <= 5) {
|
|
2034
|
-
return `string ENUM[${enumVals.map(v => JSON.stringify(v)).join(
|
|
2132
|
+
return `string ENUM[${enumVals.map((v) => JSON.stringify(v)).join(', ')}]`;
|
|
2035
2133
|
}
|
|
2036
2134
|
return `string ENUM[${enumVals.length} options]`;
|
|
2037
2135
|
}
|
|
@@ -2039,37 +2137,37 @@ function formatTypeHint(propData, depth = 0) {
|
|
|
2039
2137
|
if (propData.const !== undefined) {
|
|
2040
2138
|
return `string CONST=${JSON.stringify(propData.const)}`;
|
|
2041
2139
|
}
|
|
2042
|
-
if (type ===
|
|
2140
|
+
if (type === 'array') {
|
|
2043
2141
|
const items = propData.items;
|
|
2044
|
-
if (items && typeof items ===
|
|
2045
|
-
const itemType = items.type ??
|
|
2046
|
-
if (itemType ===
|
|
2142
|
+
if (items && typeof items === 'object') {
|
|
2143
|
+
const itemType = items.type ?? 'unknown';
|
|
2144
|
+
if (itemType === 'object') {
|
|
2047
2145
|
const nestedProps = items.properties;
|
|
2048
2146
|
const nestedReq = items.required ?? [];
|
|
2049
2147
|
if (nestedProps && depth < 1) {
|
|
2050
2148
|
const nestedList = Object.entries(nestedProps).map(([n, d]) => {
|
|
2051
|
-
const t = d.type ??
|
|
2052
|
-
const req = nestedReq.includes(n) ?
|
|
2149
|
+
const t = d.type ?? 'unknown';
|
|
2150
|
+
const req = nestedReq.includes(n) ? ' REQUIRED' : '';
|
|
2053
2151
|
return `${n}: ${t}${req}`;
|
|
2054
2152
|
});
|
|
2055
|
-
return `ARRAY_OF_OBJECTS[${nestedList.join(
|
|
2153
|
+
return `ARRAY_OF_OBJECTS[${nestedList.join(', ')}]`;
|
|
2056
2154
|
}
|
|
2057
|
-
return
|
|
2155
|
+
return 'ARRAY_OF_OBJECTS';
|
|
2058
2156
|
}
|
|
2059
2157
|
return `ARRAY_OF_${itemType.toUpperCase()}`;
|
|
2060
2158
|
}
|
|
2061
|
-
return
|
|
2159
|
+
return 'ARRAY';
|
|
2062
2160
|
}
|
|
2063
|
-
if (type ===
|
|
2161
|
+
if (type === 'object') {
|
|
2064
2162
|
const nestedProps = propData.properties;
|
|
2065
2163
|
const nestedReq = propData.required ?? [];
|
|
2066
2164
|
if (nestedProps && depth < 1) {
|
|
2067
2165
|
const nestedList = Object.entries(nestedProps).map(([n, d]) => {
|
|
2068
|
-
const t = d.type ??
|
|
2069
|
-
const req = nestedReq.includes(n) ?
|
|
2166
|
+
const t = d.type ?? 'unknown';
|
|
2167
|
+
const req = nestedReq.includes(n) ? ' REQUIRED' : '';
|
|
2070
2168
|
return `${n}: ${t}${req}`;
|
|
2071
2169
|
});
|
|
2072
|
-
return `object{${nestedList.join(
|
|
2170
|
+
return `object{${nestedList.join(', ')}}`;
|
|
2073
2171
|
}
|
|
2074
2172
|
}
|
|
2075
2173
|
return type;
|
|
@@ -2086,7 +2184,7 @@ function formatTypeHint(propData, depth = 0) {
|
|
|
2086
2184
|
* @param promptTemplate - Template for the signature (default: "\\n\\nSTRICT PARAMETERS: {params}.")
|
|
2087
2185
|
* @returns Modified tools array with signatures injected
|
|
2088
2186
|
*/
|
|
2089
|
-
export function injectParameterSignatures(tools, promptTemplate =
|
|
2187
|
+
export function injectParameterSignatures(tools, promptTemplate = '\n\n⚠️ STRICT PARAMETERS: {params}.') {
|
|
2090
2188
|
if (!tools || !Array.isArray(tools))
|
|
2091
2189
|
return tools;
|
|
2092
2190
|
return tools.map((tool) => {
|
|
@@ -2095,7 +2193,7 @@ export function injectParameterSignatures(tools, promptTemplate = "\n\n⚠️ ST
|
|
|
2095
2193
|
return tool;
|
|
2096
2194
|
const newDeclarations = declarations.map((decl) => {
|
|
2097
2195
|
// Skip if signature already injected (avoids duplicate injection)
|
|
2098
|
-
if (decl.description?.includes(
|
|
2196
|
+
if (decl.description?.includes('STRICT PARAMETERS:')) {
|
|
2099
2197
|
return decl;
|
|
2100
2198
|
}
|
|
2101
2199
|
const schema = decl.parameters || decl.parametersJsonSchema;
|
|
@@ -2108,12 +2206,12 @@ export function injectParameterSignatures(tools, promptTemplate = "\n\n⚠️ ST
|
|
|
2108
2206
|
const paramList = Object.entries(properties).map(([propName, propData]) => {
|
|
2109
2207
|
const typeHint = formatTypeHint(propData);
|
|
2110
2208
|
const isRequired = required.includes(propName);
|
|
2111
|
-
return `${propName} (${typeHint}${isRequired ?
|
|
2209
|
+
return `${propName} (${typeHint}${isRequired ? ', REQUIRED' : ''})`;
|
|
2112
2210
|
});
|
|
2113
|
-
const sigStr = promptTemplate.replace(
|
|
2211
|
+
const sigStr = promptTemplate.replace('{params}', paramList.join(', '));
|
|
2114
2212
|
return {
|
|
2115
2213
|
...decl,
|
|
2116
|
-
description: (decl.description ||
|
|
2214
|
+
description: (decl.description || '') + sigStr,
|
|
2117
2215
|
};
|
|
2118
2216
|
});
|
|
2119
2217
|
return { ...tool, functionDeclarations: newDeclarations };
|
|
@@ -2131,36 +2229,37 @@ export function injectToolHardeningInstruction(payload, instructionText) {
|
|
|
2131
2229
|
return;
|
|
2132
2230
|
// Skip if instruction already present (avoids duplicate injection)
|
|
2133
2231
|
const existing = payload.systemInstruction;
|
|
2134
|
-
if (existing && typeof existing ===
|
|
2232
|
+
if (existing && typeof existing === 'object' && 'parts' in existing) {
|
|
2135
2233
|
const parts = existing.parts;
|
|
2136
|
-
if (Array.isArray(parts) &&
|
|
2234
|
+
if (Array.isArray(parts) &&
|
|
2235
|
+
parts.some((p) => p.text?.includes('CRITICAL TOOL USAGE INSTRUCTIONS'))) {
|
|
2137
2236
|
return;
|
|
2138
2237
|
}
|
|
2139
2238
|
}
|
|
2140
2239
|
const instructionPart = { text: instructionText };
|
|
2141
2240
|
if (payload.systemInstruction) {
|
|
2142
|
-
if (existing && typeof existing ===
|
|
2241
|
+
if (existing && typeof existing === 'object' && 'parts' in existing) {
|
|
2143
2242
|
const parts = existing.parts;
|
|
2144
2243
|
if (Array.isArray(parts)) {
|
|
2145
2244
|
parts.push(instructionPart);
|
|
2146
2245
|
}
|
|
2147
2246
|
}
|
|
2148
|
-
else if (typeof existing ===
|
|
2247
|
+
else if (typeof existing === 'string') {
|
|
2149
2248
|
payload.systemInstruction = {
|
|
2150
|
-
role:
|
|
2249
|
+
role: 'user',
|
|
2151
2250
|
parts: [{ text: existing }, instructionPart],
|
|
2152
2251
|
};
|
|
2153
2252
|
}
|
|
2154
2253
|
else {
|
|
2155
2254
|
payload.systemInstruction = {
|
|
2156
|
-
role:
|
|
2255
|
+
role: 'user',
|
|
2157
2256
|
parts: [instructionPart],
|
|
2158
2257
|
};
|
|
2159
2258
|
}
|
|
2160
2259
|
}
|
|
2161
2260
|
else {
|
|
2162
2261
|
payload.systemInstruction = {
|
|
2163
|
-
role:
|
|
2262
|
+
role: 'user',
|
|
2164
2263
|
parts: [instructionPart],
|
|
2165
2264
|
};
|
|
2166
2265
|
}
|
|
@@ -2187,12 +2286,12 @@ export function assignToolIdsToContents(contents) {
|
|
|
2187
2286
|
return content;
|
|
2188
2287
|
}
|
|
2189
2288
|
const newParts = content.parts.map((part) => {
|
|
2190
|
-
if (part && typeof part ===
|
|
2289
|
+
if (part && typeof part === 'object' && part.functionCall) {
|
|
2191
2290
|
const call = { ...part.functionCall };
|
|
2192
2291
|
if (!call.id) {
|
|
2193
2292
|
call.id = `tool-call-${++toolCallCounter}`;
|
|
2194
2293
|
}
|
|
2195
|
-
const nameKey = typeof call.name ===
|
|
2294
|
+
const nameKey = typeof call.name === 'string' ? call.name : `tool-${toolCallCounter}`;
|
|
2196
2295
|
const queue = pendingCallIdsByName.get(nameKey) || [];
|
|
2197
2296
|
queue.push(call.id);
|
|
2198
2297
|
pendingCallIdsByName.set(nameKey, queue);
|
|
@@ -2221,9 +2320,9 @@ export function matchResponseIdsToContents(contents, pendingCallIdsByName) {
|
|
|
2221
2320
|
return content;
|
|
2222
2321
|
}
|
|
2223
2322
|
const newParts = content.parts.map((part) => {
|
|
2224
|
-
if (part && typeof part ===
|
|
2323
|
+
if (part && typeof part === 'object' && part.functionResponse) {
|
|
2225
2324
|
const resp = { ...part.functionResponse };
|
|
2226
|
-
if (!resp.id && typeof resp.name ===
|
|
2325
|
+
if (!resp.id && typeof resp.name === 'string') {
|
|
2227
2326
|
const queue = pendingCallIdsByName.get(resp.name);
|
|
2228
2327
|
if (queue && queue.length > 0) {
|
|
2229
2328
|
resp.id = queue.shift();
|
|
@@ -2264,7 +2363,7 @@ export function applyToolPairingFixes(payload, isClaude) {
|
|
|
2264
2363
|
// Third pass: fix orphan recovery
|
|
2265
2364
|
payload.contents = fixToolResponseGrouping(contentsWithMatchedIds);
|
|
2266
2365
|
contentsFixed = true;
|
|
2267
|
-
log.debug(
|
|
2366
|
+
log.debug('Applied tool pairing fixes to contents[]', {
|
|
2268
2367
|
originalLength: payload.contents.length,
|
|
2269
2368
|
});
|
|
2270
2369
|
}
|
|
@@ -2272,7 +2371,7 @@ export function applyToolPairingFixes(payload, isClaude) {
|
|
|
2272
2371
|
if (Array.isArray(payload.messages)) {
|
|
2273
2372
|
payload.messages = validateAndFixClaudeToolPairing(payload.messages);
|
|
2274
2373
|
messagesFixed = true;
|
|
2275
|
-
log.debug(
|
|
2374
|
+
log.debug('Applied tool pairing fixes to messages[]', {
|
|
2276
2375
|
originalLength: payload.messages.length,
|
|
2277
2376
|
});
|
|
2278
2377
|
}
|
|
@@ -2297,10 +2396,10 @@ export function createSyntheticTextResponse(text, extraHeaders = {}) {
|
|
|
2297
2396
|
candidates: [
|
|
2298
2397
|
{
|
|
2299
2398
|
content: {
|
|
2300
|
-
role:
|
|
2399
|
+
role: 'model',
|
|
2301
2400
|
parts: [{ text }],
|
|
2302
2401
|
},
|
|
2303
|
-
finishReason:
|
|
2402
|
+
finishReason: 'STOP',
|
|
2304
2403
|
},
|
|
2305
2404
|
],
|
|
2306
2405
|
usageMetadata: {
|
|
@@ -2312,17 +2411,17 @@ export function createSyntheticTextResponse(text, extraHeaders = {}) {
|
|
|
2312
2411
|
return new Response(`data: ${JSON.stringify(event)}\n\n`, {
|
|
2313
2412
|
status: 200,
|
|
2314
2413
|
headers: {
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2414
|
+
'Content-Type': 'text/event-stream',
|
|
2415
|
+
'Cache-Control': 'no-cache',
|
|
2416
|
+
Connection: 'keep-alive',
|
|
2417
|
+
'X-Antigravity-Synthetic': 'true',
|
|
2319
2418
|
...extraHeaders,
|
|
2320
2419
|
},
|
|
2321
2420
|
});
|
|
2322
2421
|
}
|
|
2323
|
-
export function createSyntheticErrorResponse(errorMessage, _requestedModel =
|
|
2422
|
+
export function createSyntheticErrorResponse(errorMessage, _requestedModel = 'unknown') {
|
|
2324
2423
|
return createSyntheticTextResponse(errorMessage, {
|
|
2325
|
-
|
|
2424
|
+
'X-Antigravity-Error-Type': 'synthetic_error',
|
|
2326
2425
|
});
|
|
2327
2426
|
}
|
|
2328
2427
|
//# sourceMappingURL=request-helpers.js.map
|