fix(geminiLive): T2 prompt durci + VAD réintégré, retrait SDK @google/genai

- Bug 1: prompt système T2 durci (13 règles absolues, interdiction du "?",
  rôle inerte) pour stopper la relance systématique. Réf TD-22.
- Bug 2: realtimeInputConfig (VAD automaticActivityDetection, 4 champs)
  réintégré dans le setup frame Gemini.
- Bug 8: @google/genai retiré + test-gemini-live.js supprimé (SDK abandonné
  au profit du WebSocket brut).

Tests 292/292 verts. Validé Golden Dataset Groupe D.
This commit is contained in:
Hermann_Kitio 2026-06-28 11:49:37 +03:00
parent eee75b53ca
commit 94387a71db
5 changed files with 39 additions and 588 deletions

415
package-lock.json generated
View file

@ -8,7 +8,6 @@
"name": "expria-backend",
"version": "1.0.0",
"dependencies": {
"@google/genai": "^1.50.1",
"@hono/node-server": "^1.13.7",
"@hono/node-ws": "^1.3.0",
"@supabase/supabase-js": "^2.49.4",
@ -542,29 +541,6 @@
"node": ">=18"
}
},
"node_modules/@google/genai": {
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz",
"integrity": "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ==",
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.3.0",
"p-retry": "^4.6.2",
"protobufjs": "^7.5.4",
"ws": "^8.18.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.2"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"optional": true
}
}
},
"node_modules/@hono/node-server": {
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
@ -671,70 +647,6 @@
"node": ">=14"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@ -1205,12 +1117,6 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@ -1369,15 +1275,6 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@ -1436,35 +1333,6 @@
"node": "18 || 20 || >=22"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
@ -1478,12 +1346,6 @@
"node": "18 || 20 || >=22"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@ -1585,19 +1447,11 @@
"node": ">= 8"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -1654,15 +1508,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -1769,12 +1614,6 @@
"node": ">=12.0.0"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@ -1793,29 +1632,6 @@
}
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@ -1833,18 +1649,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -1869,34 +1673,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
"integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/gcp-metadata": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@ -2002,32 +1778,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/google-auth-library": {
"version": "10.6.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
"integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^7.1.4",
"gcp-metadata": "8.1.2",
"google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-logging-utils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -2090,19 +1840,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
@ -2206,42 +1943,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/loupe": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
@ -2333,6 +2034,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@ -2354,44 +2056,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@ -2404,19 +2068,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -2517,30 +2168,6 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/protobufjs": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz",
"integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/qs": {
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
@ -2566,15 +2193,6 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
@ -2620,26 +2238,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@ -3235,15 +2833,6 @@
}
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View file

@ -11,7 +11,6 @@
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@google/genai": "^1.50.1",
"@hono/node-server": "^1.13.7",
"@hono/node-ws": "^1.3.0",
"@supabase/supabase-js": "^2.49.4",

View file

@ -81,8 +81,13 @@ describe("openGeminiLiveSession (raw WS)", () => {
);
expect(setup.setup.inputAudioTranscription).toEqual({});
expect(setup.setup.outputAudioTranscription).toEqual({});
// ⚠ DEBUG : realtimeInputConfig (VAD) encore absent — prochain push.
expect(setup.setup.realtimeInputConfig).toBeUndefined();
// VAD réintégré (Sprint 6d Bug 2) — cf. IMPLEMENTATION_T2_LIVE.md §3 step 7.
expect(setup.setup.realtimeInputConfig.automaticActivityDetection).toEqual({
disabled: false,
startOfSpeechSensitivity: "START_SENSITIVITY_LOW",
endOfSpeechSensitivity: "END_SENSITIVITY_LOW",
silenceDurationMs: 2000,
});
});
it("forwarde un chunk audio client {type:'audio'} en realtimeInput vers Gemini", () => {

View file

@ -1,11 +1,11 @@
/**
* geminiLive.ts Sprint 6d (revert WS brut).
*
* Le SDK `@google/genai` fermait la session sans setupComplete ni raison
* exploitable. On revient au WebSocket brut (package `ws`) qui était utilisé
* par `test-gemini-live.js` et permet de loguer précisément ce que Gemini
* répond. Config setup réduite au strict minimum tant que `setupComplete`
* n'est pas confirmé en prod ; on réintègre champs un par un ensuite.
* Historiquement, le proxy s'appuyait sur un SDK Gemini de haut niveau, mais
* celui-ci fermait la session sans setupComplete ni raison exploitable. On
* utilise désormais le WebSocket brut (package `ws`), qui permet de loguer
* précisément ce que Gemini répond et de maîtriser le contenu exact du setup
* frame (model, systemInstruction, transcriptions, VAD).
*
* Interface publique (consommée par `routes/t2live.ts`) INCHANGÉE :
* - openGeminiLiveSession(clientWs, opts)
@ -22,7 +22,7 @@ export const GEMINI_LIVE_URL =
/**
* Modèle Live cible. `gemini-2.0-flash-live-001` est le modèle Live confirmé
* par la doc Google pour les clés API Developer + Express. Format `models/...`
* dans le setup frame natif (cf. `test-gemini-live.js`).
* requis dans le setup frame natif.
*/
export const GEMINI_LIVE_MODEL = "gemini-3.1-flash-live-preview";
@ -41,22 +41,23 @@ export function buildT2SystemPrompt(input: {
contexte: string;
}): string {
const { role, contexte } = input;
return `Tu es un examinateur du TCF Canada pour l'épreuve d'Expression Orale, Tâche 2 (dialogue interactif).
RÔLE : Tu incarnes ${role}.
return `RÔLE : Tu incarnes ${role}.
CONTEXTE : ${contexte}
RÈGLES ABSOLUES :
1. Tu parles TOUJOURS en français naturel et courant, niveau B2-C1.
2. Tu NE corriges JAMAIS les erreurs du candidat.
2. Tu NE corriges JAMAIS les erreurs du candidat. Tu continues naturellement.
3. Tu attends que le candidat finisse sa question avant de répondre.
4. Tes réponses sont courtes (15 à 25 mots maximum). Pas de monologue.
4. Tes réponses sont courtes (15 à 25 mots maximum) pour laisser la place au dialogue.
5. Ne donne pas toutes les informations d'un coup. Force le candidat à poser des questions précises.
6. Si le candidat est vague, réponds brièvement sans chercher à compléter.
7. Ne pose JAMAIS de question après tes réponses. Tu réponds et tu te tais. La seule exception : si le candidat marque un long silence et semble avoir terminé, tu peux dire une seule fois « Si vous n'avez plus de questions, je vous souhaite une bonne journée » ou équivalent pour clore naturellement.
8. Ne prends jamais d'initiative pour orienter la conversation.
9. Tu peux être légèrement pressé ou hésitant pour rendre l'échange réaliste.
10. JAMAIS de listes ni de structure numérotée dans tes réponses.
11. Ne mentionne jamais que tu es une IA ou un modèle.
12. Tu ne prends PAS la parole en premier. Tu attends que le candidat s'adresse à toi.`;
6. Si le candidat est vague, réponds brièvement sans chercher à compléter c'est à lui de reformuler.
7. STRICTE INTERDICTION DE POSER DES QUESTIONS. Tu n'as pas le droit d'utiliser de point d'interrogation. Tes phrases se terminent par un point.
8. SILENCE TOTAL APRÈS LA RÉPONSE. Réponds de manière factuelle, puis arrête-toi immédiatement. Ne suggère rien, ne relance pas, ne dis pas "et vous ?".
9. RÔLE PASSIF : tu es une source d'information inerte. Tu n'aides pas le candidat à tenir la conversation. S'il ne parle plus, le silence s'installe.
10. AUCUNE FORMULE DE POLITESSE DE FIN : bannis "n'hésitez pas", "j'espère que ça vous aide", "qu'en pensez-vous ?".
11. JAMAIS de listes ni de structure numérotée parle naturellement.
12. Ne mentionne jamais que tu es une IA ou un modèle.
13. Tu ne prends PAS la parole en premier. Tu attends que le candidat s'adresse à toi.`;
}
/**
@ -212,11 +213,10 @@ function tryParseGeminiJson(data: unknown): GeminiServerMessage | null {
}
/**
* Construit le setup frame minimal Gemini Live (équivalent du mode
* `minimal` de `test-gemini-live.js`). Les champs `systemInstruction`,
* `inputAudioTranscription`, `outputAudioTranscription`,
* `realtimeInputConfig.automaticActivityDetection` sont volontairement
* retirés tant que `setupComplete` n'est pas confirmé en prod.
* Construit le setup frame Gemini Live : model + responseModalities AUDIO,
* systemInstruction (prompt T2), input/outputAudioTranscription, et
* realtimeInputConfig.automaticActivityDetection (VAD : START/END_SENSITIVITY_LOW,
* 2 s de silence avant que l'IA réponde cf. IMPLEMENTATION_T2_LIVE.md §3).
*/
function buildSetupFrame(systemPrompt: string): string {
return JSON.stringify({
@ -230,6 +230,14 @@ function buildSetupFrame(systemPrompt: string): string {
},
inputAudioTranscription: {},
outputAudioTranscription: {},
realtimeInputConfig: {
automaticActivityDetection: {
disabled: false,
startOfSpeechSensitivity: "START_SENSITIVITY_LOW",
endOfSpeechSensitivity: "END_SENSITIVITY_LOW",
silenceDurationMs: 2000,
},
},
},
});
}

View file

@ -1,150 +0,0 @@
// test-gemini-live.js — Sprint 6d : debug du setup frame Gemini Live via SDK.
//
// Usage :
// node --env-file=.env test-gemini-live.js minimal
// node --env-file=.env test-gemini-live.js +system
// node --env-file=.env test-gemini-live.js +transcription
// node --env-file=.env test-gemini-live.js +vad
//
// Chaque mode part du `minimal` qui doit fonctionner avec une clé Express
// Mode et ajoute UN champ. Si le mode reçoit `setupComplete` → le champ est
// accepté. Si l'ouverture échoue → c'est ce champ qui pose problème.
//
// Migration Sprint 6d : passage du WebSocket brut au SDK officiel
// `@google/genai` qui gère l'auth Express Mode automatiquement.
import {
GoogleGenAI,
Modality,
StartSensitivity,
EndSensitivity,
} from "@google/genai";
const MODES = ["minimal", "+system", "+transcription", "+vad"];
const mode = process.argv[2] ?? "minimal";
if (!MODES.includes(mode)) {
console.error(
`❌ Mode inconnu : "${mode}". Modes valides : ${MODES.join(", ")}`,
);
process.exit(1);
}
const KEY = process.env.GEMINI_API_KEY;
if (!KEY) {
console.error("❌ GEMINI_API_KEY manquante dans l'env");
process.exit(1);
}
// Modèle par défaut Sprint 6d. Fallback documenté : `gemini-2.0-flash-live-001`.
const MODEL = "gemini-3.1-flash-live-preview";
const SAMPLE_PROMPT =
"Tu joues le rôle d'un bailleur. Tu réponds uniquement en français. " +
"Tu attends que ton interlocuteur s'adresse à toi avant de parler.";
function buildConfig(mode) {
// Base minimal — équivalent au mode `minimal` qui doit fonctionner.
const config = {
responseModalities: [Modality.AUDIO],
};
if (mode === "+system") {
config.systemInstruction = SAMPLE_PROMPT;
}
if (mode === "+transcription") {
config.inputAudioTranscription = {};
config.outputAudioTranscription = {};
}
if (mode === "+vad") {
config.realtimeInputConfig = {
automaticActivityDetection: {
disabled: false,
startOfSpeechSensitivity: StartSensitivity.START_SENSITIVITY_LOW,
endOfSpeechSensitivity: EndSensitivity.END_SENSITIVITY_LOW,
silenceDurationMs: 2000,
},
};
}
return config;
}
const ai = new GoogleGenAI({ vertexai: true, apiKey: KEY });
console.log(`→ Mode : ${mode}`);
console.log(`→ Modèle : ${MODEL}`);
console.log("→ Connexion à Gemini Live (via SDK)…");
let setupCompleteReceived = false;
let resolved = false;
const config = buildConfig(mode);
console.log("→ Config envoyée :");
console.log(JSON.stringify(config, null, 2));
const timeoutId = setTimeout(() => {
if (!resolved) {
console.log("⏱ Timeout 15 s — pas de setupComplete reçu.");
process.exit(setupCompleteReceived ? 0 : 1);
}
}, 15000);
try {
const session = await ai.live.connect({
model: MODEL,
config,
callbacks: {
onopen: () => {
console.log("✅ Connexion ouverte");
},
onmessage: (msg) => {
// Compat : selon la version du SDK, setupComplete arrive soit comme
// propriété directe, soit dans serverContent. On loggue tout.
console.log("📨 Message reçu :", JSON.stringify(msg).slice(0, 600));
if (msg.setupComplete || msg?.serverContent?.setupComplete) {
setupCompleteReceived = true;
resolved = true;
console.log(
`\n🎉 [${mode}] ACCEPTÉ — setupComplete reçu (modèle ${MODEL}).`,
);
clearTimeout(timeoutId);
try {
session.close();
} catch {
/* ignore */
}
process.exit(0);
}
},
onerror: (err) => {
console.log("❌ Erreur :", err?.message ?? err);
},
onclose: (evt) => {
console.log(
`🔒 Fermeture${evt?.code ? ` — code ${evt.code}` : ""}${evt?.reason ? ` reason: ${evt.reason}` : ""}`,
);
if (!setupCompleteReceived) {
console.log(`\n⚠ [${mode}] REJETÉ — fermeture avant setupComplete.`);
console.log(
"→ Le ou les champs ajoutés par ce mode ne sont pas acceptés.",
);
}
resolved = true;
clearTimeout(timeoutId);
process.exit(setupCompleteReceived ? 0 : 1);
},
},
});
// Conserver la session vivante jusqu'au timeout/setupComplete.
void session;
} catch (err) {
resolved = true;
clearTimeout(timeoutId);
console.log(
"❌ live.connect a échoué :",
err instanceof Error ? err.message : String(err),
);
process.exit(1);
}