From 9141f62785a48a5b67bec76a091b7383ee453233 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:15:57 -0700 Subject: [PATCH 01/23] chore(harness-grok-build): scaffold package and build configs Co-Authored-By: Claude Sonnet 4.6 --- packages/harness-grok-build/package.json | 73 ++++++ .../harness-grok-build/src/bridge/index.ts | 1 + .../src/bridge/package.json | 11 + .../src/bridge/pnpm-lock.yaml | 113 ++++++++ packages/harness-grok-build/src/index.ts | 1 + .../harness-grok-build/tsconfig.build.json | 1 + packages/harness-grok-build/tsconfig.json | 10 + packages/harness-grok-build/tsup.config.ts | 21 ++ packages/harness-grok-build/turbo.json | 1 + .../harness-grok-build/vitest.node.config.js | 7 + pnpm-lock.yaml | 248 +++++++++++++++--- 11 files changed, 445 insertions(+), 42 deletions(-) create mode 100644 packages/harness-grok-build/package.json create mode 100644 packages/harness-grok-build/src/bridge/index.ts create mode 100644 packages/harness-grok-build/src/bridge/package.json create mode 100644 packages/harness-grok-build/src/bridge/pnpm-lock.yaml create mode 100644 packages/harness-grok-build/src/index.ts create mode 100644 packages/harness-grok-build/tsconfig.build.json create mode 100644 packages/harness-grok-build/tsconfig.json create mode 100644 packages/harness-grok-build/tsup.config.ts create mode 100644 packages/harness-grok-build/turbo.json create mode 100644 packages/harness-grok-build/vitest.node.config.js diff --git a/packages/harness-grok-build/package.json b/packages/harness-grok-build/package.json new file mode 100644 index 000000000000..83bb08f047a0 --- /dev/null +++ b/packages/harness-grok-build/package.json @@ -0,0 +1,73 @@ +{ + "name": "@ai-sdk/harness-grok-build", + "version": "1.0.0-beta.0", + "type": "module", + "license": "Apache-2.0", + "sideEffects": false, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "source": "./src/index.ts", + "files": [ + "dist/**/*", + "src", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "!src/**/__snapshots__", + "!src/**/__fixtures__", + "CHANGELOG.md", + "README.md" + ], + "scripts": { + "build": "pnpm clean && tsup --tsconfig tsconfig.build.json && pnpm copy-bridge-assets", + "build:watch": "pnpm clean && tsup --watch", + "clean": "del-cli dist *.tsbuildinfo", + "copy-bridge-assets": "node -e \"import('node:fs/promises').then(async fs => { await fs.copyFile('src/bridge/package.json', 'dist/bridge/package.json'); await fs.copyFile('src/bridge/pnpm-lock.yaml', 'dist/bridge/pnpm-lock.yaml'); })\"", + "type-check": "tsc --build", + "test": "pnpm test:node", + "test:watch": "vitest --config vitest.node.config.js", + "test:node": "vitest --config vitest.node.config.js --run" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@ai-sdk/harness": "workspace:*", + "@ai-sdk/provider-utils": "workspace:*", + "ws": "^8.20.1", + "zod": "3.25.76" + }, + "devDependencies": { + "@types/node": "22.19.19", + "@types/ws": "^8.5.13", + "@vercel/ai-tsconfig": "workspace:*", + "tsup": "^8.5.1", + "typescript": "5.8.3" + }, + "engines": { + "node": ">=22" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "homepage": "https://ai-sdk.dev/docs", + "repository": { + "type": "git", + "url": "https://github.com/vercel/ai", + "directory": "packages/harness-grok-build" + }, + "bugs": { + "url": "https://github.com/vercel/ai/issues" + }, + "keywords": [ + "ai", + "harness", + "grok", + "grok-build" + ] +} diff --git a/packages/harness-grok-build/src/bridge/index.ts b/packages/harness-grok-build/src/bridge/index.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/packages/harness-grok-build/src/bridge/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/harness-grok-build/src/bridge/package.json b/packages/harness-grok-build/src/bridge/package.json new file mode 100644 index 000000000000..905a3be13db4 --- /dev/null +++ b/packages/harness-grok-build/src/bridge/package.json @@ -0,0 +1,11 @@ +{ + "name": "harness-grok-build-bridge", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@xai-official/grok": "0.2.51", + "ws": "8.20.1", + "zod": "3.25.76" + } +} diff --git a/packages/harness-grok-build/src/bridge/pnpm-lock.yaml b/packages/harness-grok-build/src/bridge/pnpm-lock.yaml new file mode 100644 index 000000000000..e8879ac67039 --- /dev/null +++ b/packages/harness-grok-build/src/bridge/pnpm-lock.yaml @@ -0,0 +1,113 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xai-official/grok': + specifier: 0.2.51 + version: 0.2.51 + ws: + specifier: 8.20.1 + version: 8.20.1 + zod: + specifier: 3.25.76 + version: 3.25.76 + +packages: + + '@iarna/toml@3.0.0': + resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} + + '@xai-official/grok-darwin-arm64@0.2.51': + resolution: {integrity: sha512-HKkXN+1ui1P4SqRJNIWgjMZZEP47+1H+utNxD0R/cUPHOZd7oP7si/fNJMk8BVwTxkDtNuOtoIdHxt1TinwgmA==} + cpu: [arm64] + os: [darwin] + + '@xai-official/grok-darwin-x64@0.2.51': + resolution: {integrity: sha512-JAQ/VLnbkvhgvFMsmesX2HaCLcp+hEu88koLTG344rSJJ2VSCv5SZGYS6zTPlvBV5E54v/fq9RdMSahM2HLt5A==} + cpu: [x64] + os: [darwin] + + '@xai-official/grok-linux-arm64@0.2.51': + resolution: {integrity: sha512-pnAizhZolOYu9swOx7STTanzfWRm5vyW6jkh4TsQd0SjRDj8UsNgkRdhXS72Sk6dxfQd1/0BAROl8ogs1+/NYQ==} + cpu: [arm64] + os: [linux] + + '@xai-official/grok-linux-x64@0.2.51': + resolution: {integrity: sha512-TmJPsUETGgfxKyDg3ra7mmcdWIBIRgKDJ1NKPOj7RzkaskLv3QXiX7n8oXkxcLAoXsz9RRvsSCUMaDYFHDgrOQ==} + cpu: [x64] + os: [linux] + + '@xai-official/grok-win32-arm64@0.2.51': + resolution: {integrity: sha512-VTNoLVgtQAvvIx03fzqb51jga1czD7tjtl2l53DaeY23MakSr4VZp/2XTX+39J9rM2GnqwZ2xTL0oH48eoHtmQ==} + cpu: [arm64] + os: [win32] + + '@xai-official/grok-win32-x64@0.2.51': + resolution: {integrity: sha512-qp0krbn1GHuV+mlHes9v13NlmgAaI53QE8MpqiXr74Jyn4aho+vHbRAJbKTQsHcpoOeqAunl0zNbtRVyLxoVGw==} + cpu: [x64] + os: [win32] + + '@xai-official/grok@0.2.51': + resolution: {integrity: sha512-HZp/7PljrHeT/bKwzZ+fwOlKi9Q42O+kB9G5LA2Pv3xTLa1Sr2eIIqIMjb8e0cOC/qVsFZkEe/wNCqU+R/bnoA==} + engines: {node: '>=20'} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@iarna/toml@3.0.0': {} + + '@xai-official/grok-darwin-arm64@0.2.51': + optional: true + + '@xai-official/grok-darwin-x64@0.2.51': + optional: true + + '@xai-official/grok-linux-arm64@0.2.51': + optional: true + + '@xai-official/grok-linux-x64@0.2.51': + optional: true + + '@xai-official/grok-win32-arm64@0.2.51': + optional: true + + '@xai-official/grok-win32-x64@0.2.51': + optional: true + + '@xai-official/grok@0.2.51': + dependencies: + '@iarna/toml': 3.0.0 + optionalDependencies: + '@xai-official/grok-darwin-arm64': 0.2.51 + '@xai-official/grok-darwin-x64': 0.2.51 + '@xai-official/grok-linux-arm64': 0.2.51 + '@xai-official/grok-linux-x64': 0.2.51 + '@xai-official/grok-win32-arm64': 0.2.51 + '@xai-official/grok-win32-x64': 0.2.51 + + ws@8.20.1: {} + + zod@3.25.76: {} diff --git a/packages/harness-grok-build/src/index.ts b/packages/harness-grok-build/src/index.ts new file mode 100644 index 000000000000..aed3b137e46b --- /dev/null +++ b/packages/harness-grok-build/src/index.ts @@ -0,0 +1 @@ +export const grokBuildPlaceholder = true; diff --git a/packages/harness-grok-build/tsconfig.build.json b/packages/harness-grok-build/tsconfig.build.json new file mode 100644 index 000000000000..fbc3852add7a --- /dev/null +++ b/packages/harness-grok-build/tsconfig.build.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json", "compilerOptions": {"composite": false}} diff --git a/packages/harness-grok-build/tsconfig.json b/packages/harness-grok-build/tsconfig.json new file mode 100644 index 000000000000..2d31269fd57d --- /dev/null +++ b/packages/harness-grok-build/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "./node_modules/@vercel/ai-tsconfig/ts-library.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist" + }, + "exclude": ["dist", "build", "node_modules", "tsup.config.ts"], + "references": [{ "path": "../harness" }, { "path": "../provider-utils" }] +} diff --git a/packages/harness-grok-build/tsup.config.ts b/packages/harness-grok-build/tsup.config.ts new file mode 100644 index 000000000000..46d75096eb81 --- /dev/null +++ b/packages/harness-grok-build/tsup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'tsup'; +export default defineConfig([ + { + entry: { index: 'src/index.ts' }, + format: ['esm'], + target: 'es2022', + dts: true, + sourcemap: true, + }, + { + entry: { 'bridge/index': 'src/bridge/index.ts' }, + format: ['esm'], + target: 'es2022', + outExtension: () => ({ js: '.mjs' }), + dts: false, + sourcemap: true, + platform: 'node', + noExternal: ['@ai-sdk/harness'], + external: ['@xai-official/grok', 'ws', 'zod'], + }, +]); diff --git a/packages/harness-grok-build/turbo.json b/packages/harness-grok-build/turbo.json new file mode 100644 index 000000000000..a4dcd04164c8 --- /dev/null +++ b/packages/harness-grok-build/turbo.json @@ -0,0 +1 @@ +{"extends": ["//"], "tasks": {"build": {"outputs": ["**/dist/**"]}}} diff --git a/packages/harness-grok-build/vitest.node.config.js b/packages/harness-grok-build/vitest.node.config.js new file mode 100644 index 000000000000..346cc2a14c36 --- /dev/null +++ b/packages/harness-grok-build/vitest.node.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; +export default defineConfig({ + test: { + environment: 'node', + include: ['**/*.test.ts', '**/*.test.tsx'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 927edeee96f0..1d44083df636 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,7 +173,7 @@ importers: version: 0.545.0(react@18.3.1) next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -625,7 +625,7 @@ importers: version: 2.1.1 next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -834,7 +834,7 @@ importers: version: 7.2.2 ts-jest: specifier: ^29.4.9 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.7 version: 9.5.7(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.3(@swc/helpers@0.5.21))(lightningcss@1.32.0)) @@ -861,7 +861,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -919,7 +919,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -971,7 +971,7 @@ importers: version: 1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1017,7 +1017,7 @@ importers: version: 1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1075,7 +1075,7 @@ importers: version: 0.561.0(react@18.3.1) next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1127,7 +1127,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1173,7 +1173,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1231,7 +1231,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1283,7 +1283,7 @@ importers: version: 0.55.0(@opentelemetry/api@1.9.1) '@sentry/nextjs': specifier: ^10.53.1 - version: 10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) + version: 10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) '@sentry/opentelemetry': specifier: 8.22.0 version: 8.22.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) @@ -1295,7 +1295,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1347,7 +1347,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1402,7 +1402,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1504,7 +1504,7 @@ importers: version: 3.5.12 nuxt: specifier: 3.21.7 - version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) tailwindcss: specifier: 3.4.15 version: 3.4.15(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)) @@ -2597,6 +2597,37 @@ importers: specifier: 5.8.3 version: 5.8.3 + packages/harness-grok-build: + dependencies: + '@ai-sdk/harness': + specifier: workspace:* + version: link:../harness + '@ai-sdk/provider-utils': + specifier: workspace:* + version: link:../provider-utils + ws: + specifier: ^8.20.1 + version: 8.21.0 + zod: + specifier: 3.25.76 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: 22.19.19 + version: 22.19.19 + '@types/ws': + specifier: ^8.5.13 + version: 8.18.1 + '@vercel/ai-tsconfig': + specifier: workspace:* + version: link:../../tools/tsconfig + tsup: + specifier: ^8.5.1 + version: 8.5.1(@swc/core@1.15.3(@swc/helpers@0.5.21))(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.0)(typescript@5.8.3)(yaml@2.9.0) + typescript: + specifier: 5.8.3 + version: 5.8.3 + packages/harness-pi: dependencies: '@ai-sdk/harness': @@ -22215,21 +22246,45 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -22261,16 +22316,34 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + optional: true + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + optional: true + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -22286,41 +22359,89 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + optional: true + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -26657,7 +26778,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3)': + '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.7(magicast@0.5.3) @@ -26674,8 +26795,8 @@ snapshots: impound: 1.1.5 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16) - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(encoding@0.1.13)(oxc-parser@0.132.0)(srvx@0.11.16) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -26756,7 +26877,7 @@ snapshots: '@nuxt/ui-templates@1.3.4': {} - '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.7(magicast@0.5.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.0) @@ -26775,7 +26896,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) nypm: 0.6.7 ohash: 2.0.11 pathe: 2.0.3 @@ -29118,7 +29239,7 @@ snapshots: '@sentry/types': 8.22.0 '@sentry/utils': 8.22.0 - '@sentry/nextjs@10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15))': + '@sentry/nextjs@10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.41.1 @@ -29131,7 +29252,7 @@ snapshots: '@sentry/react': 10.53.1(react@18.3.1) '@sentry/vercel-edge': 10.53.1 '@sentry/webpack-plugin': 5.3.0(encoding@0.1.13)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) - next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) rollup: 4.61.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -30280,7 +30401,7 @@ snapshots: dependencies: '@upstash/redis': 1.24.3 - '@vercel/nft@1.10.2(rollup@4.62.0)': + '@vercel/nft@1.10.2(encoding@0.1.13)(rollup@4.62.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) '@rollup/pluginutils': 5.4.0(rollup@4.62.0) @@ -31077,7 +31198,7 @@ snapshots: semver: 7.7.4 watchpack: 2.5.1 optionalDependencies: - next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) transitivePeerDependencies: - '@opentelemetry/api' - '@swc/helpers' @@ -31721,6 +31842,20 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-loader@10.0.0(@babel/core@7.28.3)(webpack@5.105.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.12)): dependencies: '@babel/core': 7.28.3 @@ -31814,12 +31949,39 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + optional: true + babel-preset-jest@29.6.3(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + babel-preset-jest@29.6.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + optional: true + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -34266,7 +34428,7 @@ snapshots: geist@1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)): dependencies: - next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) generator-function@2.0.1: {} @@ -35732,7 +35894,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.20.1 + ws: 8.21.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -37204,7 +37366,7 @@ snapshots: neo-async@2.6.2: {} - next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): + next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): dependencies: '@next/env': 15.5.18 '@swc/helpers': 0.5.15 @@ -37212,7 +37374,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.18 '@next/swc-darwin-x64': 15.5.18 @@ -37309,7 +37471,7 @@ snapshots: - babel-plugin-macros optional: true - nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16): + nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(encoding@0.1.13)(oxc-parser@0.132.0)(srvx@0.11.16): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.0) @@ -37319,7 +37481,7 @@ snapshots: '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.0) '@rollup/plugin-replace': 6.0.3(rollup@4.62.0) '@rollup/plugin-terser': 1.0.0(rollup@4.62.0) - '@vercel/nft': 1.10.2(rollup@4.62.0) + '@vercel/nft': 1.10.2(encoding@0.1.13)(rollup@4.62.0) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.3) chokidar: 5.0.0 @@ -37598,16 +37760,16 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): + nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@dxup/nuxt': 0.4.1(magicast@0.5.3)(typescript@5.9.3) '@nuxt/cli': 3.35.2(@nuxt/schema@3.21.7)(cac@6.7.14)(commander@13.1.0)(magicast@0.5.3) '@nuxt/devtools': 3.2.4(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) '@nuxt/kit': 3.21.7(magicast@0.5.3) - '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3) + '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3) '@nuxt/schema': 3.21.7 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.7(magicast@0.5.3)) - '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3)) '@vue/shared': 3.5.38 c12: 3.3.4(magicast@0.5.3) @@ -40231,10 +40393,12 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.29.7)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 + optionalDependencies: + '@babel/core': 7.29.7 styled-jsx@5.1.6(react@19.0.0-rc-cc1ec60d0d-20240607): dependencies: @@ -40809,7 +40973,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -40823,10 +40987,10 @@ snapshots: typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.29.7) jest-util: 29.7.0 ts-loader@9.5.7(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.3(@swc/helpers@0.5.21))(lightningcss@1.32.0)): @@ -40934,7 +41098,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) resolve-from: 5.0.0 - rollup: 4.61.1 + rollup: 4.62.0 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 1.0.2 @@ -40963,7 +41127,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.0)(yaml@2.9.0) resolve-from: 5.0.0 - rollup: 4.61.1 + rollup: 4.62.0 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 1.0.2 @@ -40992,7 +41156,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.0)(yaml@2.9.0) resolve-from: 5.0.0 - rollup: 4.61.1 + rollup: 4.62.0 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 1.0.2 @@ -42072,7 +42236,7 @@ snapshots: sockjs: 0.3.24 spdy: 4.0.2 webpack-dev-middleware: 7.4.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.12)) - ws: 8.20.1 + ws: 8.21.0 optionalDependencies: webpack: 5.105.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.12) transitivePeerDependencies: From 325cb0537699b42224cfce072d20e3ac5fdf0f0f Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:22:57 -0700 Subject: [PATCH 02/23] chore(harness-grok-build): add root tsconfig reference and grok devDependency Co-Authored-By: Claude Sonnet 4.6 --- packages/harness-grok-build/package.json | 1 + pnpm-lock.yaml | 281 +++++++++-------------- tsconfig.json | 3 + 3 files changed, 115 insertions(+), 170 deletions(-) diff --git a/packages/harness-grok-build/package.json b/packages/harness-grok-build/package.json index 83bb08f047a0..e8d58c1d9424 100644 --- a/packages/harness-grok-build/package.json +++ b/packages/harness-grok-build/package.json @@ -42,6 +42,7 @@ "zod": "3.25.76" }, "devDependencies": { + "@xai-official/grok": "0.2.51", "@types/node": "22.19.19", "@types/ws": "^8.5.13", "@vercel/ai-tsconfig": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d44083df636..1cfec5f7836e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,7 +173,7 @@ importers: version: 0.545.0(react@18.3.1) next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -625,7 +625,7 @@ importers: version: 2.1.1 next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -834,7 +834,7 @@ importers: version: 7.2.2 ts-jest: specifier: ^29.4.9 - version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.7 version: 9.5.7(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.3(@swc/helpers@0.5.21))(lightningcss@1.32.0)) @@ -861,7 +861,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -919,7 +919,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -971,7 +971,7 @@ importers: version: 1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1017,7 +1017,7 @@ importers: version: 1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1075,7 +1075,7 @@ importers: version: 0.561.0(react@18.3.1) next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1127,7 +1127,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1173,7 +1173,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1231,7 +1231,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1283,7 +1283,7 @@ importers: version: 0.55.0(@opentelemetry/api@1.9.1) '@sentry/nextjs': specifier: ^10.53.1 - version: 10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) + version: 10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) '@sentry/opentelemetry': specifier: 8.22.0 version: 8.22.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) @@ -1295,7 +1295,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1347,7 +1347,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1402,7 +1402,7 @@ importers: version: link:../../packages/ai next: specifier: ^15.5.18 - version: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -1504,7 +1504,7 @@ importers: version: 3.5.12 nuxt: specifier: 3.21.7 - version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) tailwindcss: specifier: 3.4.15 version: 3.4.15(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)) @@ -2621,6 +2621,9 @@ importers: '@vercel/ai-tsconfig': specifier: workspace:* version: link:../../tools/tsconfig + '@xai-official/grok': + specifier: 0.2.51 + version: 0.2.51 tsup: specifier: ^8.5.1 version: 8.5.1(@swc/core@1.15.3(@swc/helpers@0.5.21))(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.0)(typescript@5.8.3)(yaml@2.9.0) @@ -6471,6 +6474,9 @@ packages: hono: '>=3.9.0' zod: ^3.25.0 || ^4.0.0 + '@iarna/toml@3.0.0': + resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -12208,6 +12214,43 @@ packages: peerDependencies: zod: 4.3.6 + '@xai-official/grok-darwin-arm64@0.2.51': + resolution: {integrity: sha512-HKkXN+1ui1P4SqRJNIWgjMZZEP47+1H+utNxD0R/cUPHOZd7oP7si/fNJMk8BVwTxkDtNuOtoIdHxt1TinwgmA==} + cpu: [arm64] + os: [darwin] + + '@xai-official/grok-darwin-x64@0.2.51': + resolution: {integrity: sha512-JAQ/VLnbkvhgvFMsmesX2HaCLcp+hEu88koLTG344rSJJ2VSCv5SZGYS6zTPlvBV5E54v/fq9RdMSahM2HLt5A==} + cpu: [x64] + os: [darwin] + + '@xai-official/grok-linux-arm64@0.2.51': + resolution: {integrity: sha512-pnAizhZolOYu9swOx7STTanzfWRm5vyW6jkh4TsQd0SjRDj8UsNgkRdhXS72Sk6dxfQd1/0BAROl8ogs1+/NYQ==} + cpu: [arm64] + os: [linux] + + '@xai-official/grok-linux-x64@0.2.51': + resolution: {integrity: sha512-TmJPsUETGgfxKyDg3ra7mmcdWIBIRgKDJ1NKPOj7RzkaskLv3QXiX7n8oXkxcLAoXsz9RRvsSCUMaDYFHDgrOQ==} + cpu: [x64] + os: [linux] + + '@xai-official/grok-win32-arm64@0.2.51': + resolution: {integrity: sha512-VTNoLVgtQAvvIx03fzqb51jga1czD7tjtl2l53DaeY23MakSr4VZp/2XTX+39J9rM2GnqwZ2xTL0oH48eoHtmQ==} + cpu: [arm64] + os: [win32] + + '@xai-official/grok-win32-x64@0.2.51': + resolution: {integrity: sha512-qp0krbn1GHuV+mlHes9v13NlmgAaI53QE8MpqiXr74Jyn4aho+vHbRAJbKTQsHcpoOeqAunl0zNbtRVyLxoVGw==} + cpu: [x64] + os: [win32] + + '@xai-official/grok@0.2.51': + resolution: {integrity: sha512-HZp/7PljrHeT/bKwzZ+fwOlKi9Q42O+kB9G5LA2Pv3xTLa1Sr2eIIqIMjb8e0cOC/qVsFZkEe/wNCqU+R/bnoA==} + engines: {node: '>=20'} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + '@xhmikosr/archive-type@8.1.0': resolution: {integrity: sha512-EXOjEbnZFE5c/nFMf4FOrEURVanzHpnkPYmnmr78u02/8hAhE0FMq8p9TK1IM0/bFr5VcyBUY0gfLm8f7dKy+Q==} engines: {node: '>=20'} @@ -22246,45 +22289,21 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -22316,34 +22335,16 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - optional: true - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - optional: true - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -22359,89 +22360,41 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -24469,6 +24422,8 @@ snapshots: hono: 4.12.25 zod: 3.25.76 + '@iarna/toml@3.0.0': {} + '@iconify/types@2.0.0': {} '@iconify/utils@3.1.3': @@ -26778,7 +26733,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3)': + '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.7(magicast@0.5.3) @@ -26795,8 +26750,8 @@ snapshots: impound: 1.1.5 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(encoding@0.1.13)(oxc-parser@0.132.0)(srvx@0.11.16) - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -26877,7 +26832,7 @@ snapshots: '@nuxt/ui-templates@1.3.4': {} - '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.7(magicast@0.5.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.0) @@ -26896,7 +26851,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) nypm: 0.6.7 ohash: 2.0.11 pathe: 2.0.3 @@ -29239,7 +29194,7 @@ snapshots: '@sentry/types': 8.22.0 '@sentry/utils': 8.22.0 - '@sentry/nextjs@10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15))': + '@sentry/nextjs@10.53.1(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(encoding@0.1.13)(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.41.1 @@ -29252,7 +29207,7 @@ snapshots: '@sentry/react': 10.53.1(react@18.3.1) '@sentry/vercel-edge': 10.53.1 '@sentry/webpack-plugin': 5.3.0(encoding@0.1.13)(webpack@5.105.0(lightningcss@1.32.0)(postcss@8.5.15)) - next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) rollup: 4.61.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -30401,7 +30356,7 @@ snapshots: dependencies: '@upstash/redis': 1.24.3 - '@vercel/nft@1.10.2(encoding@0.1.13)(rollup@4.62.0)': + '@vercel/nft@1.10.2(rollup@4.62.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) '@rollup/pluginutils': 5.4.0(rollup@4.62.0) @@ -31198,7 +31153,7 @@ snapshots: semver: 7.7.4 watchpack: 2.5.1 optionalDependencies: - next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) transitivePeerDependencies: - '@opentelemetry/api' - '@swc/helpers' @@ -31398,6 +31353,35 @@ snapshots: ulid: 3.0.2 zod: 4.3.6 + '@xai-official/grok-darwin-arm64@0.2.51': + optional: true + + '@xai-official/grok-darwin-x64@0.2.51': + optional: true + + '@xai-official/grok-linux-arm64@0.2.51': + optional: true + + '@xai-official/grok-linux-x64@0.2.51': + optional: true + + '@xai-official/grok-win32-arm64@0.2.51': + optional: true + + '@xai-official/grok-win32-x64@0.2.51': + optional: true + + '@xai-official/grok@0.2.51': + dependencies: + '@iarna/toml': 3.0.0 + optionalDependencies: + '@xai-official/grok-darwin-arm64': 0.2.51 + '@xai-official/grok-darwin-x64': 0.2.51 + '@xai-official/grok-linux-arm64': 0.2.51 + '@xai-official/grok-linux-x64': 0.2.51 + '@xai-official/grok-win32-arm64': 0.2.51 + '@xai-official/grok-win32-x64': 0.2.51 + '@xhmikosr/archive-type@8.1.0': dependencies: file-type: 21.3.4 @@ -31842,20 +31826,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-jest@29.7.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.7) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - optional: true - babel-loader@10.0.0(@babel/core@7.28.3)(webpack@5.105.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.12)): dependencies: '@babel/core': 7.28.3 @@ -31949,39 +31919,12 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) - optional: true - babel-preset-jest@29.6.3(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) - babel-preset-jest@29.6.3(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) - optional: true - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -34428,7 +34371,7 @@ snapshots: geist@1.7.0(next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)): dependencies: - next: 15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) generator-function@2.0.1: {} @@ -37366,7 +37309,7 @@ snapshots: neo-async@2.6.2: {} - next@15.5.18(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): + next@15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): dependencies: '@next/env': 15.5.18 '@swc/helpers': 0.5.15 @@ -37374,7 +37317,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.29.7)(react@18.3.1) + styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.18 '@next/swc-darwin-x64': 15.5.18 @@ -37471,7 +37414,7 @@ snapshots: - babel-plugin-macros optional: true - nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(encoding@0.1.13)(oxc-parser@0.132.0)(srvx@0.11.16): + nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.0) @@ -37481,7 +37424,7 @@ snapshots: '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.0) '@rollup/plugin-replace': 6.0.3(rollup@4.62.0) '@rollup/plugin-terser': 1.0.0(rollup@4.62.0) - '@vercel/nft': 1.10.2(encoding@0.1.13)(rollup@4.62.0) + '@vercel/nft': 1.10.2(rollup@4.62.0) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.3) chokidar: 5.0.0 @@ -37760,16 +37703,16 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): + nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@dxup/nuxt': 0.4.1(magicast@0.5.3)(typescript@5.9.3) '@nuxt/cli': 3.35.2(@nuxt/schema@3.21.7)(cac@6.7.14)(commander@13.1.0)(magicast@0.5.3) '@nuxt/devtools': 3.2.4(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) '@nuxt/kit': 3.21.7(magicast@0.5.3) - '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3) + '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3) '@nuxt/schema': 3.21.7 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.7(magicast@0.5.3)) - '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(encoding@0.1.13)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3)) '@vue/shared': 3.5.38 c12: 3.3.4(magicast@0.5.3) @@ -40393,12 +40336,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.29.7)(react@18.3.1): + styled-jsx@5.1.6(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 - optionalDependencies: - '@babel/core': 7.29.7 styled-jsx@5.1.6(react@19.0.0-rc-cc1ec60d0d-20240607): dependencies: @@ -40973,7 +40914,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -40987,10 +40928,10 @@ snapshots: typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) + babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 29.7.0 ts-loader@9.5.7(typescript@5.9.3)(webpack@5.105.0(@swc/core@1.15.3(@swc/helpers@0.5.21))(lightningcss@1.32.0)): diff --git a/tsconfig.json b/tsconfig.json index e537f9b9c4fb..ae486df8b044 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -84,6 +84,9 @@ { "path": "packages/harness-codex" }, + { + "path": "packages/harness-grok-build" + }, { "path": "packages/harness-pi" }, From 1217577976d7964ed2e76135cd28c9fbec456753 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:24:24 -0700 Subject: [PATCH 03/23] feat(harness-grok-build): add auth resolver Co-Authored-By: Claude Sonnet 4.6 --- .../src/grok-build-auth.test.ts | 38 ++++++++++ .../harness-grok-build/src/grok-build-auth.ts | 69 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 packages/harness-grok-build/src/grok-build-auth.test.ts create mode 100644 packages/harness-grok-build/src/grok-build-auth.ts diff --git a/packages/harness-grok-build/src/grok-build-auth.test.ts b/packages/harness-grok-build/src/grok-build-auth.test.ts new file mode 100644 index 000000000000..c5a2453e7b96 --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-auth.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import { resolveGrokBuildEnv } from './grok-build-auth'; + +describe('resolveGrokBuildEnv', () => { + it('uses explicit xai api key', () => { + const env = resolveGrokBuildEnv({ xai: { apiKey: 'sk-explicit' } }, {}); + expect(env.XAI_API_KEY).toBe('sk-explicit'); + }); + + it('falls back to XAI_API_KEY from process env', () => { + const env = resolveGrokBuildEnv(undefined, { XAI_API_KEY: 'sk-env' }); + expect(env.XAI_API_KEY).toBe('sk-env'); + }); + + it('prefers gateway auth from env over direct xai', () => { + const env = resolveGrokBuildEnv(undefined, { + AI_GATEWAY_API_KEY: 'gw-key', + XAI_API_KEY: 'sk-env', + }); + expect(env.AI_GATEWAY_API_KEY).toBe('gw-key'); + }); + + it('pins to explicit gateway when given', () => { + const env = resolveGrokBuildEnv( + { gateway: { apiKey: 'gw-explicit' } }, + { XAI_API_KEY: 'sk-env' }, + ); + expect(env.AI_GATEWAY_API_KEY).toBe('gw-explicit'); + }); + + it('passes through a custom base url', () => { + const env = resolveGrokBuildEnv( + { xai: { apiKey: 'k', baseUrl: 'https://x' } }, + {}, + ); + expect(env.XAI_BASE_URL).toBe('https://x'); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts new file mode 100644 index 000000000000..cd87957fb77d --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -0,0 +1,69 @@ +import { getAiGatewayAuthFromEnv } from '@ai-sdk/harness/utils'; + +export type GrokBuildAuthOptions = { + readonly xai?: { + readonly apiKey?: string; + readonly baseUrl?: string; + }; + readonly gateway?: { + readonly apiKey?: string; + readonly baseUrl?: string; + }; +}; + +/** + * Resolve the environment-variable blob the bridge needs to authenticate the + * `grok` CLI (directly with xAI, or via the Vercel AI Gateway). Precedence: + * 1. Explicit `auth.xai` — pin to direct xAI auth. + * 2. Explicit `auth.gateway` — pin to gateway-routed auth. + * 3. Auto-detect: gateway first (`AI_GATEWAY_API_KEY` / `VERCEL_OIDC_TOKEN`), + * then direct (`XAI_API_KEY`). + */ +export function resolveGrokBuildEnv( + auth: GrokBuildAuthOptions | undefined, + processEnv: Record = process.env, +): Record { + if (auth?.xai) { + return pickXai(auth.xai, processEnv); + } + + const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: processEnv }); + if (auth?.gateway) { + return pickGateway(auth.gateway, gatewayAuthFromEnv); + } + if (gatewayAuthFromEnv.apiKey) { + return pickGateway({}, gatewayAuthFromEnv); + } + + return pickXai({}, processEnv); +} + +function pickXai( + explicit: NonNullable, + processEnv: Record, +): Record { + const env: Record = {}; + const apiKey = explicit.apiKey ?? processEnv.XAI_API_KEY; + if (apiKey) env.XAI_API_KEY = apiKey; + const baseUrl = explicit.baseUrl ?? processEnv.XAI_BASE_URL; + if (baseUrl) env.XAI_BASE_URL = baseUrl; + return env; +} + +function pickGateway( + explicit: NonNullable, + gatewayAuthFromEnv: ReturnType, +): Record { + const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey; + const baseUrl = explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl; + const env: Record = {}; + if (apiKey) { + env.AI_GATEWAY_API_KEY = apiKey; + env.XAI_API_KEY = apiKey; + } + if (baseUrl) { + env.AI_GATEWAY_BASE_URL = baseUrl; + env.XAI_BASE_URL = baseUrl; + } + return env; +} From 9506844dc997c2dca269c40cd60565fd3274acee Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:25:36 -0700 Subject: [PATCH 04/23] refactor(harness-grok-build): always forward gateway base url in auth resolver Co-Authored-By: Claude Opus 4.8 --- packages/harness-grok-build/src/grok-build-auth.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts index cd87957fb77d..98e738a53f10 100644 --- a/packages/harness-grok-build/src/grok-build-auth.ts +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -61,9 +61,9 @@ function pickGateway( env.AI_GATEWAY_API_KEY = apiKey; env.XAI_API_KEY = apiKey; } - if (baseUrl) { - env.AI_GATEWAY_BASE_URL = baseUrl; - env.XAI_BASE_URL = baseUrl; - } + // Always forward the gateway base URL (mirrors claude-code-auth); the gateway + // helper always returns a non-empty default. + env.AI_GATEWAY_BASE_URL = baseUrl; + env.XAI_BASE_URL = baseUrl; return env; } From a4348d1122455874207142044e26913dde194a9c Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:26:36 -0700 Subject: [PATCH 05/23] feat(harness-grok-build): add bridge protocol start schema Co-Authored-By: Claude Sonnet 4.6 --- .../src/grok-build-bridge-protocol.test.ts | 29 +++++++++++++++ .../src/grok-build-bridge-protocol.ts | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts create mode 100644 packages/harness-grok-build/src/grok-build-bridge-protocol.ts diff --git a/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts b/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts new file mode 100644 index 000000000000..ccc235b5b4da --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; +import { + inboundMessageSchema, + startMessageSchema, +} from './grok-build-bridge-protocol'; + +describe('grok-build bridge protocol', () => { + it('accepts a minimal start message', () => { + const parsed = startMessageSchema.parse({ type: 'start', prompt: 'hi' }); + expect(parsed.type).toBe('start'); + }); + + it('accepts grok-specific fields', () => { + const parsed = startMessageSchema.parse({ + type: 'start', + prompt: 'hi', + model: 'grok-build-0.1', + planMode: true, + continue: true, + }); + expect(parsed.model).toBe('grok-build-0.1'); + expect(parsed.planMode).toBe(true); + }); + + it('discriminates start within the inbound union', () => { + const parsed = inboundMessageSchema.parse({ type: 'start', prompt: 'hi' }); + expect(parsed.type).toBe('start'); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-bridge-protocol.ts b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts new file mode 100644 index 000000000000..7357e87914d0 --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts @@ -0,0 +1,36 @@ +import { + harnessV1BridgeInboundCommandSchemas, + harnessV1BridgeOutboundMessageSchema, + harnessV1BridgeReadySchema, + harnessV1BridgeStartBaseSchema, +} from '@ai-sdk/harness'; +import { z } from 'zod/v4'; + +/* + * Grok Build's bridge wire protocol. The outbound events, transport frames, + * shared inbound commands, and `bridge-ready` line all come from the shared + * `@ai-sdk/harness` protocol. The only Grok-specific piece is the `start` + * payload, which carries Grok CLI configuration. + */ + +export const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema; +export type OutboundMessage = z.infer; + +export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({ + model: z.string().optional(), + // Grok Build's plan-first execution loop. + planMode: z.boolean().optional(), + // Resume signal. When true, the bridge resumes the prior CLI thread in the + // workdir instead of starting a fresh session. + continue: z.boolean().optional(), +}); +export type StartMessage = z.infer; + +export const inboundMessageSchema = z.discriminatedUnion('type', [ + startMessageSchema, + ...harnessV1BridgeInboundCommandSchemas, +]); +export type InboundMessage = z.infer; + +export const bridgeReadySchema = harnessV1BridgeReadySchema; +export type BridgeReady = z.infer; From 69ee1129d5d916a5d16649e7694cffaba7607aa1 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:34:52 -0700 Subject: [PATCH 06/23] Add GROK_BUILD_BUILTIN_TOOLS (read/write/edit/bash/glob/grep/webSearch) defined via the shared commonTool helper --- .../src/grok-build-harness.test.ts | 18 +++ .../src/grok-build-harness.ts | 116 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 packages/harness-grok-build/src/grok-build-harness.test.ts create mode 100644 packages/harness-grok-build/src/grok-build-harness.ts diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts new file mode 100644 index 000000000000..4561121e56e2 --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { GROK_BUILD_BUILTIN_TOOLS, toCommonName } from './grok-build-harness'; + +describe('grok-build builtin tools', () => { + it('exposes common tool names', () => { + expect(Object.keys(GROK_BUILD_BUILTIN_TOOLS)).toEqual( + expect.arrayContaining(['read', 'write', 'edit', 'bash']), + ); + }); + + it('maps a native name to its common name', () => { + expect(toCommonName('Read')).toBe('read'); + }); + + it('passes through unknown native names unchanged', () => { + expect(toCommonName('SomeGrokSpecificTool')).toBe('SomeGrokSpecificTool'); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts new file mode 100644 index 000000000000..29134cf61cef --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -0,0 +1,116 @@ +import { + commonTool, + type HarnessV1BuiltinTool, + type HarnessV1BuiltinToolName, +} from '@ai-sdk/harness'; +import { z } from 'zod'; + +/* + * Native tool name → common harness name mapping. + * + * TODO: Reconcile these native keys against captured Grok Build CLI fixture + * output once real CLI traces are available. The names below are borrowed from + * the Claude Code harness as a placeholder and may differ from what Grok Build + * actually emits. + */ +export const NATIVE_TO_COMMON: Readonly< + Record +> = { + Read: 'read', + Write: 'write', + Edit: 'edit', + Bash: 'bash', + Glob: 'glob', + Grep: 'grep', + WebSearch: 'webSearch', +}; + +/** + * Map a native Grok Build tool name to its cross-harness common name. + * Returns the native name unchanged if no mapping is found. + */ +export function toCommonName( + nativeName: string, +): HarnessV1BuiltinToolName | string { + return NATIVE_TO_COMMON[nativeName] ?? nativeName; +} + +/* + * Every native tool the Grok Build CLI can invoke, declared as a ToolSet + * keyed by the common name where a mapping exists. + * + * TODO: Reconcile native tool names and input schemas against captured Grok + * Build CLI fixture output once real CLI traces are available. + */ +export const GROK_BUILD_BUILTIN_TOOLS = { + read: commonTool('read', { + nativeName: 'Read', + toolUseKind: 'readonly', + description: 'Read file contents (text, image, PDF, notebook)', + inputSchema: z.object({ + file_path: z.string(), + offset: z.number().optional(), + limit: z.number().optional(), + pages: z.string().optional(), + }), + }), + write: commonTool('write', { + nativeName: 'Write', + toolUseKind: 'edit', + description: 'Overwrite or create a file at an absolute path', + inputSchema: z.object({ + file_path: z.string(), + content: z.string(), + }), + }), + edit: commonTool('edit', { + nativeName: 'Edit', + toolUseKind: 'edit', + description: 'Edit a file by exact string replacement', + inputSchema: z.object({ + file_path: z.string(), + old_string: z.string(), + new_string: z.string(), + replace_all: z.boolean().optional(), + }), + }), + bash: commonTool('bash', { + nativeName: 'Bash', + toolUseKind: 'bash', + description: 'Execute a shell command', + inputSchema: z.object({ + command: z.string(), + timeout: z.number().optional(), + description: z.string().optional(), + run_in_background: z.boolean().optional(), + }), + }), + glob: commonTool('glob', { + nativeName: 'Glob', + toolUseKind: 'readonly', + description: 'Fast file-pattern search using glob syntax', + inputSchema: z.object({ + pattern: z.string(), + path: z.string().optional(), + }), + }), + grep: commonTool('grep', { + nativeName: 'Grep', + toolUseKind: 'readonly', + description: 'Regex search over file contents', + inputSchema: z.object({ + pattern: z.string(), + path: z.string().optional(), + }), + }), + webSearch: commonTool('webSearch', { + nativeName: 'WebSearch', + toolUseKind: 'readonly', + description: 'Issue web search queries', + inputSchema: z.object({ + query: z.string(), + allowed_domains: z.array(z.string()).optional(), + blocked_domains: z.array(z.string()).optional(), + }), + }), +} as const satisfies Record>; From c7f71271969f68fa5344b12c9f4b12e2e44385b7 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Wed, 17 Jun 2026 16:43:38 -0700 Subject: [PATCH 07/23] feat(harness-grok-build): add createGrokBuild factory and bootstrap recipe --- .../src/grok-build-harness.test.ts | 37 ++++++++- .../src/grok-build-harness.ts | 78 +++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts index 4561121e56e2..9c02fb3b6f11 100644 --- a/packages/harness-grok-build/src/grok-build-harness.test.ts +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -1,5 +1,24 @@ -import { describe, expect, it } from 'vitest'; -import { GROK_BUILD_BUILTIN_TOOLS, toCommonName } from './grok-build-harness'; +import type * as NodeFsPromises from 'node:fs/promises'; +import { describe, expect, it, vi } from 'vitest'; +import { + createGrokBuild, + GROK_BUILD_BUILTIN_TOOLS, + toCommonName, +} from './grok-build-harness'; + +vi.mock('node:fs/promises', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + readFile: vi.fn(async (input: unknown, ...rest: unknown[]) => { + const p = String(input); + if (p.endsWith('/bridge/index.mjs')) return '// mock bridge\n'; + if (p.endsWith('/bridge/package.json')) return '{"name":"mock"}'; + if (p.endsWith('/bridge/pnpm-lock.yaml')) return 'lockfileVersion: 9\n'; + return actual.readFile(input as any, ...(rest as any[])); + }), + }; +}); describe('grok-build builtin tools', () => { it('exposes common tool names', () => { @@ -16,3 +35,17 @@ describe('grok-build builtin tools', () => { expect(toCommonName('SomeGrokSpecificTool')).toBe('SomeGrokSpecificTool'); }); }); + +describe('grok-build bootstrap', () => { + it('produces a bootstrap recipe under the adapter-owned dir', async () => { + const harness = createGrokBuild(); + const bootstrap = await harness.getBootstrap!(); + expect(bootstrap.harnessId).toBe('grok-build'); + expect(bootstrap.bootstrapDir).toBe('/tmp/harness/grok-build'); + const paths = bootstrap.files.map(f => f.path); + expect(paths).toContain('/tmp/harness/grok-build/package.json'); + expect(paths).toContain('/tmp/harness/grok-build/pnpm-lock.yaml'); + expect(paths).toContain('/tmp/harness/grok-build/bridge.mjs'); + expect(bootstrap.commands.some(c => c.command.includes('pnpm'))).toBe(true); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 29134cf61cef..2292e1ce5476 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -1,9 +1,14 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import { commonTool, + type HarnessV1, + type HarnessV1Bootstrap, type HarnessV1BuiltinTool, type HarnessV1BuiltinToolName, } from '@ai-sdk/harness'; import { z } from 'zod'; +import { type GrokBuildAuthOptions } from './grok-build-auth'; /* * Native tool name → common harness name mapping. @@ -114,3 +119,76 @@ export const GROK_BUILD_BUILTIN_TOOLS = { }), }), } as const satisfies Record>; + +const BOOTSTRAP_DIR = '/tmp/harness/grok-build'; + +export type GrokBuildHarnessSettings = { + readonly model?: string; + readonly planMode?: boolean; + readonly auth?: GrokBuildAuthOptions; + readonly port?: number; +}; + +async function readBridgeAsset(name: string): Promise { + const candidates = [ + new URL(`./bridge/${name}`, import.meta.url), + new URL(`../bridge/${name}`, import.meta.url), + ]; + let lastErr: unknown; + for (const url of candidates) { + try { + return await readFile(fileURLToPath(url), 'utf8'); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + if (code !== 'ENOENT') throw err; + lastErr = err; + } + } + throw lastErr ?? new Error(`bridge asset not found: ${name}`); +} + +export function createGrokBuild( + // Renamed to `settings` once doStart consumes it (turn-driver task). + _settings: GrokBuildHarnessSettings = {}, +): HarnessV1 { + // Per-instance cache: bridge assets are static, but keeping this in the + // factory closure (rather than module scope) avoids leaking state across + // separate createGrokBuild() instances. + let cachedBootstrap: HarnessV1Bootstrap | null = null; + return { + specificationVersion: 'harness-v1', + harnessId: 'grok-build', + builtinTools: GROK_BUILD_BUILTIN_TOOLS, + supportsBuiltinToolApprovals: false, + getBootstrap: async () => { + if (cachedBootstrap != null) return cachedBootstrap; + const [pkg, lock, bridge] = await Promise.all([ + readBridgeAsset('package.json'), + readBridgeAsset('pnpm-lock.yaml'), + readBridgeAsset('index.mjs'), + ]); + cachedBootstrap = { + harnessId: 'grok-build', + bootstrapDir: BOOTSTRAP_DIR, + files: [ + { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg }, + { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock }, + { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge }, + ], + commands: [ + { command: `mkdir -p ${BOOTSTRAP_DIR}` }, + { + command: `pnpm --dir ${BOOTSTRAP_DIR} install --frozen-lockfile --store-dir ${BOOTSTRAP_DIR}/.pnpm-store`, + }, + { + command: `cd ${BOOTSTRAP_DIR} && ./node_modules/.bin/grok --version`, + }, + ], + }; + return cachedBootstrap; + }, + doStart: async () => { + throw new Error('not implemented yet'); + }, + }; +} From 918cd4c2369784cf02d29fa6f2bb2286e244ba57 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 09:46:47 -0700 Subject: [PATCH 08/23] feat(harness-grok-build): map streaming-json output to harness stream parts --- .../src/__fixtures__/README.md | 33 +++ .../__fixtures__/streaming-json-basic.jsonl | 140 ++++++++++++ .../src/grok-build-stream-map.test.ts | 55 +++++ .../src/grok-build-stream-map.ts | 216 ++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 packages/harness-grok-build/src/__fixtures__/README.md create mode 100644 packages/harness-grok-build/src/__fixtures__/streaming-json-basic.jsonl create mode 100644 packages/harness-grok-build/src/grok-build-stream-map.test.ts create mode 100644 packages/harness-grok-build/src/grok-build-stream-map.ts diff --git a/packages/harness-grok-build/src/__fixtures__/README.md b/packages/harness-grok-build/src/__fixtures__/README.md new file mode 100644 index 000000000000..b9c23d2121ee --- /dev/null +++ b/packages/harness-grok-build/src/__fixtures__/README.md @@ -0,0 +1,33 @@ +# Grok Build CLI fixtures + +## `streaming-json-basic.jsonl` (REAL capture — source of truth) + +Real output of: + + grok -p "Create a file hello.txt containing the text hi, then read it back." \ + -m grok-build-0.1 --output-format streaming-json --always-approve + +captured against `@xai-official/grok` v0.2.53 (direct xAI API, `XAI_API_KEY`) on +2026-06-18. Home-directory path in the assistant text was redacted to +`/Users/USER`. + +### Actual schema (flat, newline-delimited JSON) +This mode is lean. Only three event types appear: + +- `{"type":"thought","data":""}` — reasoning/thinking text delta +- `{"type":"text","data":""}` — assistant message text delta +- `{"type":"end","stopReason":"EndTurn","sessionId":"","requestId":""}` — terminal + +### What this mode does NOT include (important) +- **No tool-call / tool-result events** — even though the agent created and read + the file, `streaming-json` does not surface tool invocations. +- **No file-change events.** +- **No token usage.** + +Full tool/file/usage fidelity requires the `grok agent` ACP (JSON-RPC stdio) +surface instead — that's a planned follow-up (see the harness-grok-build plan). +The v1 adapter maps only thought→reasoning, text→text, end→finish. + +### stopReason values +Observed: `EndTurn`. Others (e.g. max-tokens, cancellation) are unconfirmed — +map defensively. diff --git a/packages/harness-grok-build/src/__fixtures__/streaming-json-basic.jsonl b/packages/harness-grok-build/src/__fixtures__/streaming-json-basic.jsonl new file mode 100644 index 000000000000..1fd21bc869bf --- /dev/null +++ b/packages/harness-grok-build/src/__fixtures__/streaming-json-basic.jsonl @@ -0,0 +1,140 @@ +{"type":"thought","data":"The"} +{"type":"thought","data":" user"} +{"type":"thought","data":" wants"} +{"type":"thought","data":" me"} +{"type":"thought","data":" to"} +{"type":"thought","data":":\n"} +{"type":"thought","data":"1"} +{"type":"thought","data":"."} +{"type":"thought","data":" Create"} +{"type":"thought","data":" a"} +{"type":"thought","data":" file"} +{"type":"thought","data":" hello"} +{"type":"thought","data":".txt"} +{"type":"thought","data":" containing"} +{"type":"thought","data":" the"} +{"type":"thought","data":" text"} +{"type":"thought","data":" \""} +{"type":"thought","data":"hi"} +{"type":"thought","data":"\""} +{"type":"thought","data":"The"} +{"type":"thought","data":" write"} +{"type":"thought","data":" succeeded"} +{"type":"thought","data":" but"} +{"type":"thought","data":" the"} +{"type":"thought","data":" read"} +{"type":"thought","data":" failed"} +{"type":"thought","data":"."} +{"type":"thought","data":" The"} +{"type":"thought","data":" file"} +{"type":"thought","data":" path"} +{"type":"thought","data":" might"} +{"type":"thought","data":" be"} +{"type":"thought","data":" different"} +{"type":"thought","data":"."} +{"type":"thought","data":" The"} +{"type":"thought","data":" user"} +{"type":"thought","data":"_info"} +{"type":"thought","data":" says"} +{"type":"thought","data":" \""} +{"type":"thought","data":"Prefer"} +{"type":"thought","data":" using"} +{"type":"thought","data":" relative"} +{"type":"thought","data":" paths"} +{"type":"thought","data":" over"} +{"type":"thought","data":" absolute"} +{"type":"thought","data":" paths"} +{"type":"thought","data":" as"} +{"type":"thought","data":" tool"} +{"type":"thought","data":" call"} +{"type":"thought","data":" args"} +{"type":"thought","data":" when"} +{"type":"thought","data":" possible"} +{"type":"thought","data":".\"\n"} +{"type":"thought","data":"The"} +{"type":"thought","data":" file"} +{"type":"thought","data":" hello"} +{"type":"thought","data":".txt"} +{"type":"thought","data":" exists"} +{"type":"thought","data":" in"} +{"type":"thought","data":" the"} +{"type":"thought","data":" directory"} +{"type":"thought","data":" listing"} +{"type":"thought","data":"."} +{"type":"thought","data":" The"} +{"type":"thought","data":" previous"} +{"type":"thought","data":" read"} +{"type":"thought","data":"_file"} +{"type":"thought","data":" with"} +{"type":"thought","data":" absolute"} +{"type":"thought","data":" path"} +{"type":"thought","data":" failed"} +{"type":"thought","data":" for"} +{"type":"thought","data":" some"} +{"type":"thought","data":" reason"} +{"type":"thought","data":"."} +{"type":"thought","data":" I"} +{"type":"thought","data":" should"} +{"type":"thought","data":" try"} +{"type":"thought","data":" reading"} +{"type":"thought","data":" it"} +{"type":"thought","data":" again"} +{"type":"thought","data":","} +{"type":"thought","data":" preferably"} +{"type":"thought","data":" with"} +{"type":"thought","data":" relative"} +{"type":"thought","data":" path"} +{"type":"thought","data":" \""} +{"type":"thought","data":"hello"} +{"type":"thought","data":".txt"} +{"type":"thought","data":"\".\n"} +{"type":"thought","data":"Now"} +{"type":"thought","data":" it"} +{"type":"thought","data":" worked"} +{"type":"thought","data":"."} +{"type":"thought","data":" The"} +{"type":"thought","data":" file"} +{"type":"thought","data":" hello"} +{"type":"thought","data":".txt"} +{"type":"thought","data":" was"} +{"type":"thought","data":" created"} +{"type":"thought","data":" with"} +{"type":"thought","data":" content"} +{"type":"thought","data":" \""} +{"type":"thought","data":"hi"} +{"type":"thought","data":"\""} +{"type":"thought","data":" and"} +{"type":"thought","data":" read"} +{"type":"thought","data":" back"} +{"type":"thought","data":" successfully"} +{"type":"thought","data":".\n"} +{"type":"text","data":"Done"} +{"type":"text","data":"."} +{"type":"text","data":" Created"} +{"type":"text","data":" ["} +{"type":"text","data":"hello"} +{"type":"text","data":".txt"} +{"type":"text","data":"](/"} +{"type":"text","data":"Users"} +{"type":"text","data":"/m"} +{"type":"text","data":"lek"} +{"type":"text","data":"hi"} +{"type":"text","data":"/g"} +{"type":"text","data":"rok"} +{"type":"text","data":"-c"} +{"type":"text","data":"apture"} +{"type":"text","data":"/hello"} +{"type":"text","data":".txt"} +{"type":"text","data":")"} +{"type":"text","data":" with"} +{"type":"text","data":" content"} +{"type":"text","data":" `"} +{"type":"text","data":"hi"} +{"type":"text","data":"`,"} +{"type":"text","data":" and"} +{"type":"text","data":" read"} +{"type":"text","data":" it"} +{"type":"text","data":" back"} +{"type":"text","data":" successfully"} +{"type":"text","data":"."} +{"type":"end","stopReason":"EndTurn","sessionId":"019edb94-5baf-7d21-a522-0bd8a0eeb623","requestId":"88c192b4-e774-4a3c-8e76-4eb9f816cdee"} diff --git a/packages/harness-grok-build/src/grok-build-stream-map.test.ts b/packages/harness-grok-build/src/grok-build-stream-map.test.ts new file mode 100644 index 000000000000..80f055b9502d --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-stream-map.test.ts @@ -0,0 +1,55 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { createStreamMapState, mapStreamLine } from './grok-build-stream-map'; + +const lines = readFileSync( + join(__dirname, '__fixtures__/streaming-json-basic.jsonl'), + 'utf8', +) + .split('\n') + .filter(Boolean); + +const mapAll = () => { + const s = createStreamMapState(); + return lines.flatMap(l => mapStreamLine(l, s)); +}; + +describe('mapStreamLine (grok streaming-json)', () => { + it('emits exactly one stream-start', () => { + expect(mapAll().filter(p => p.type === 'stream-start')).toHaveLength(1); + }); + it('maps thought chunks to reasoning start/delta/end', () => { + const t = mapAll().map(p => p.type); + expect(t).toContain('reasoning-start'); + expect(t).toContain('reasoning-delta'); + expect(t).toContain('reasoning-end'); + }); + it('maps text chunks to text start/delta/end', () => { + const t = mapAll().map(p => p.type); + expect(t).toContain('text-start'); + expect(t).toContain('text-delta'); + expect(t).toContain('text-end'); + }); + it('reasoning ends before text starts (single ordered transition)', () => { + const types = mapAll().map(p => p.type); + const firstText = types.indexOf('text-start'); + const reasoningEnd = types.indexOf('reasoning-end'); + expect(reasoningEnd).toBeGreaterThanOrEqual(0); + expect(firstText).toBeGreaterThan(reasoningEnd); + }); + it('concatenated text deltas reconstruct the final answer', () => { + const text = mapAll() + .filter(p => p.type === 'text-delta') + .map((p: any) => p.delta) + .join(''); + expect(text).toContain('hello.txt'); + }); + it('emits a finish for the end event', () => { + const f = mapAll().find(p => p.type === 'finish'); + expect(f).toBeDefined(); + }); + it('never throws on malformed input', () => { + expect(mapStreamLine('not json', createStreamMapState())).toEqual([]); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-stream-map.ts b/packages/harness-grok-build/src/grok-build-stream-map.ts new file mode 100644 index 000000000000..8208a31cf146 --- /dev/null +++ b/packages/harness-grok-build/src/grok-build-stream-map.ts @@ -0,0 +1,216 @@ +import type { HarnessV1StreamPart } from '@ai-sdk/harness'; + +// Extract V4 types from the finish part shape rather than importing from +// @ai-sdk/provider directly (not listed in package.json dependencies). +type FinishPart = Extract; +type LanguageModelV4FinishReason = FinishPart['finishReason']; +type LanguageModelV4Usage = FinishPart['totalUsage']; + +// --------------------------------------------------------------------------- +// State +// --------------------------------------------------------------------------- + +export type StreamMapState = { + /** Whether we have already emitted a stream-start event. */ + streamStarted: boolean; + /** Id of the currently open text block, or null. */ + openTextId: string | null; + /** Id of the currently open reasoning block, or null. */ + openReasoningId: string | null; + /** Counter used to mint unique block ids. */ + nextId: number; +}; + +export function createStreamMapState(): StreamMapState { + return { + streamStarted: false, + openTextId: null, + openReasoningId: null, + nextId: 0, + }; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function mintId(state: StreamMapState, prefix: string): string { + return `${prefix}_${state.nextId++}`; +} + +/** + * Zero usage object. + * + * NOTE: token usage is unavailable in `grok --output-format streaming-json` + * mode — the CLI does not emit usage data in this surface. Reporting zeros + * here is intentional; real usage figures will be available once the ACP + * (Agent Communication Protocol) surface is supported (future follow-up). + */ +function unknownUsage(): LanguageModelV4Usage { + // streaming-json mode reports no token counts. Use `undefined` (not 0) so + // downstream consumers can distinguish "not reported" from "zero tokens used". + // Real usage will be available via the ACP surface (future follow-up). + return { + inputTokens: { + total: undefined, + noCache: undefined, + cacheRead: undefined, + cacheWrite: undefined, + }, + outputTokens: { total: undefined, text: undefined, reasoning: undefined }, + }; +} + +function mapStopReason(raw: string | undefined): LanguageModelV4FinishReason { + switch (raw) { + case 'EndTurn': + return { unified: 'stop', raw }; + case 'MaxTokens': + return { unified: 'length', raw }; + case 'ToolUse': + return { unified: 'tool-calls', raw }; + case 'ContentFilter': + return { unified: 'content-filter', raw }; + case 'Error': + return { unified: 'error', raw }; + default: + return { unified: 'other', raw }; + } +} + +// --------------------------------------------------------------------------- +// Core mapping +// --------------------------------------------------------------------------- + +/** + * Map one raw (newline-delimited) JSON line from `grok -p ... --output-format + * streaming-json` to zero or more `HarnessV1StreamPart` events. + * + * Three event shapes exist in this mode: + * - `{"type":"thought","data":""}` — reasoning text delta + * - `{"type":"text","data":""}` — assistant text delta + * - `{"type":"end","stopReason":"EndTurn","sessionId":"...","requestId":"..."}` — terminal + * + * Tool-call/tool-result/file-change events and token usage are NOT emitted + * here — they are unavailable in this CLI surface and are a future follow-up + * via the ACP surface. + * + * Pure function with mutable state passed in — no I/O, never throws. + */ +export function mapStreamLine( + rawLine: string, + state: StreamMapState, +): HarnessV1StreamPart[] { + // Safe parse — return [] on any error, never throw. + // NOTE: `safeParseJSON` from `@ai-sdk/provider-utils` is async and cannot be + // used in a synchronous line processor. We use a local try/catch here which + // is semantically equivalent to the sync core of `safeParseJSON` (no schema + // validation needed — we validate shapes via runtime property access below). + let msg: unknown; + try { + msg = JSON.parse(rawLine); + } catch { + return []; + } + if (typeof msg !== 'object' || msg === null) return []; + + const anyMsg = msg as Record; + const eventType = anyMsg['type'] as string | undefined; + + const parts: HarnessV1StreamPart[] = []; + + // Emit stream-start exactly once, before any other event. + function ensureStreamStart() { + if (!state.streamStarted) { + state.streamStarted = true; + parts.push({ type: 'stream-start' }); + } + } + + // Close an open text block if any. + function closeTextBlock() { + if (state.openTextId !== null) { + parts.push({ type: 'text-end', id: state.openTextId }); + state.openTextId = null; + } + } + + // Close an open reasoning block if any. + function closeReasoningBlock() { + if (state.openReasoningId !== null) { + parts.push({ type: 'reasoning-end', id: state.openReasoningId }); + state.openReasoningId = null; + } + } + + ensureStreamStart(); + + switch (eventType) { + case 'thought': { + const data = typeof anyMsg['data'] === 'string' ? anyMsg['data'] : ''; + + // If a text block is somehow open, close it first (shouldn't normally happen). + closeTextBlock(); + + // Open reasoning block if not already open. + if (state.openReasoningId === null) { + const id = mintId(state, 'reasoning'); + state.openReasoningId = id; + parts.push({ type: 'reasoning-start', id }); + } + + parts.push({ + type: 'reasoning-delta', + id: state.openReasoningId, + delta: data, + }); + break; + } + + case 'text': { + const data = typeof anyMsg['data'] === 'string' ? anyMsg['data'] : ''; + + // Close any open reasoning block before switching to text. + closeReasoningBlock(); + + // Open text block if not already open. + if (state.openTextId === null) { + const id = mintId(state, 'text'); + state.openTextId = id; + parts.push({ type: 'text-start', id }); + } + + parts.push({ + type: 'text-delta', + id: state.openTextId, + delta: data, + }); + break; + } + + case 'end': { + // Close any open blocks. + closeReasoningBlock(); + closeTextBlock(); + + const stopReason = anyMsg['stopReason'] as string | undefined; + + parts.push({ + type: 'finish', + finishReason: mapStopReason(stopReason), + // NOTE: usage is unavailable in streaming-json mode; zeros are intentional. + // Real token counts will be available via the ACP surface (future follow-up). + totalUsage: unknownUsage(), + }); + break; + } + + default: { + // Unknown event type → raw passthrough. + parts.push({ type: 'raw', rawValue: msg }); + break; + } + } + + return parts; +} From 9096156f85d009aba034bddfcb50ec7a3153dd13 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 11:42:32 -0700 Subject: [PATCH 09/23] add bridge turn driver and doStart --- .changeset/grok-build-turn-driver.md | 7 + .../harness-grok-build/src/bridge/index.ts | 204 +++++- .../src/grok-build-auth.test.ts | 31 +- .../harness-grok-build/src/grok-build-auth.ts | 32 + .../src/grok-build-harness.test.ts | 274 +++++++- .../src/grok-build-harness.ts | 586 +++++++++++++++++- packages/harness-grok-build/src/index.ts | 13 +- 7 files changed, 1132 insertions(+), 15 deletions(-) create mode 100644 .changeset/grok-build-turn-driver.md diff --git a/.changeset/grok-build-turn-driver.md b/.changeset/grok-build-turn-driver.md new file mode 100644 index 000000000000..a922e61d7241 --- /dev/null +++ b/.changeset/grok-build-turn-driver.md @@ -0,0 +1,7 @@ +--- +'@ai-sdk/harness-grok-build': patch +--- + +feat(harness-grok-build): implement bridge turn driver and doStart + +The grok-build harness now runs a real turn end-to-end: it spawns the `grok` CLI inside the sandbox with `--output-format streaming-json --always-approve`, streams stdout through `mapStreamLine`, and emits `HarnessV1StreamPart` events. Adds `toGrokCliEnv` to map resolved auth onto the CLI's real env vars (direct `XAI_API_KEY` vs gateway `GROK_CODE_XAI_API_KEY` + `GROK_MODELS_BASE_URL`) and a `createSession` implementation covering prompt/continue turns, detach/stop/suspend lifecycle, and destroy. diff --git a/packages/harness-grok-build/src/bridge/index.ts b/packages/harness-grok-build/src/bridge/index.ts index cb0ff5c3b541..467d90b50395 100644 --- a/packages/harness-grok-build/src/bridge/index.ts +++ b/packages/harness-grok-build/src/bridge/index.ts @@ -1 +1,203 @@ -export {}; +// Long-running process that runs alongside the `grok` CLI in the sandbox. +// The generic transport — WebSocket server, token auth, single-flight +// reconnect, the in-memory event log + `seq`, resume replay, and the +// lifecycle/meta files — lives in the shared `@ai-sdk/harness/bridge` runtime. +// This file supplies only the Grok-specific turn driver. +// +// Grok is CLI-driven: a fresh `grok -p --output-format streaming-json` +// child is spawned per turn. Because the CLI runs with `--always-approve`, all +// tools execute *inside* grok in the sandbox — there is NO host tool dispatch in +// this mode (no relay, no MCP shim). `turn.requestToolResult` / +// `requestToolApproval` are therefore never used here. + +import { + runBridge, + type BridgeEvent, + type BridgeTurn, +} from '@ai-sdk/harness/bridge'; +import { spawn } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { argv, env as procEnv, stdout } from 'node:process'; +import { createInterface } from 'node:readline'; +import type { StartMessage } from '../grok-build-bridge-protocol'; +import { createStreamMapState, mapStreamLine } from '../grok-build-stream-map'; + +const DEFAULT_GROK_MODEL = 'grok-build-0.1'; + +const args = parseArgs(argv.slice(2)); +if (!args.workdir) { + emitFatal('Missing --workdir argument.'); +} +if (!args.bridgeStateDir) { + emitFatal('Missing --bridge-state-dir argument.'); +} +const workdir: string = args.workdir; +const bridgeStateDir: string = args.bridgeStateDir; +const bootstrapDir: string = args.bootstrapDir ?? workdir; + +// The latest grok CLI session id, learned from the terminal `end` event's +// `sessionId`. Returned to the host on detach so a future process could resume +// the grok thread via `-r/--resume`. +const sessionState: { id: string | undefined } = { id: undefined }; + +await runBridge({ + bridgeType: 'grok-build', + bridgeStateDir, + onStart: runTurn, + onDetach: () => (sessionState.id ? { sessionId: sessionState.id } : {}), +}); + +async function runTurn(start: StartMessage, turn: BridgeTurn): Promise { + const emit = (event: BridgeEvent) => turn.emit(event); + + const grokBin = resolveGrokBinary(bootstrapDir); + const cliArgs = [ + '-p', + start.prompt, + '-m', + start.model ?? DEFAULT_GROK_MODEL, + '--output-format', + 'streaming-json', + // REQUIRED in headless mode: without it the CLI blocks on tool approval. + // Tools therefore execute inside grok; no host dispatch happens here. + '--always-approve', + '--cwd', + workdir, + ]; + // Resume the prior CLI thread in this workdir instead of starting fresh. + if (start.continue) cliArgs.push('-c'); + + const child = spawn(grokBin, cliArgs, { + cwd: workdir, + env: procEnv, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const childStdout = child.stdout; + const childStderr = child.stderr; + if (!childStdout || !childStderr) { + throw new Error('grok child process did not expose stdout/stderr pipes.'); + } + + // Wire host abort to killing the child. + const onAbort = () => { + try { + child.kill('SIGTERM'); + } catch {} + }; + if (turn.abortSignal.aborted) { + onAbort(); + } else { + turn.abortSignal.addEventListener('abort', onAbort, { once: true }); + } + + // Per-turn stream-map state: each line of grok's streaming-json stdout maps + // to zero or more HarnessV1StreamPart events. + const state = createStreamMapState(); + + const rl = createInterface({ input: childStdout, crlfDelay: Infinity }); + rl.on('line', line => { + const trimmed = line.trim(); + if (trimmed.length === 0) return; + // Capture the grok session id from the terminal `end` event before mapping + // (mapStreamLine does not surface it). + captureSessionId(trimmed); + for (const part of mapStreamLine(trimmed, state)) { + emit(part as BridgeEvent); + } + }); + + // Forward stderr to this process's stderr so a CLI failure is inspectable + // from the host's bridge-stderr forwarding. + const stderrChunks: string[] = []; + childStderr.setEncoding('utf8'); + childStderr.on('data', (chunk: string) => { + stderrChunks.push(chunk); + process.stderr.write(chunk); + }); + + await new Promise((resolve, reject) => { + child.on('error', err => { + emit({ type: 'error', error: serialiseError(err) }); + reject(err); + }); + child.on('close', code => { + turn.abortSignal.removeEventListener('abort', onAbort); + // Aborted: treat as a clean wind-down (host already settles the turn). + if (turn.abortSignal.aborted) { + resolve(); + return; + } + if (code === 0) { + resolve(); + return; + } + const tail = stderrChunks.join('').trim().slice(-2000); + const err = new Error( + `grok CLI exited with code ${code}${tail ? `:\n${tail}` : ''}`, + ); + emit({ type: 'error', error: serialiseError(err) }); + reject(err); + }); + }); + + void turn.pendingUserMessages; // accepted but unused: each turn is a fresh CLI invocation. +} + +function captureSessionId(line: string): void { + try { + const msg = JSON.parse(line) as Record; + if ( + msg?.type === 'end' && + typeof msg.sessionId === 'string' && + msg.sessionId.length > 0 + ) { + sessionState.id = msg.sessionId; + } + } catch { + // Non-JSON / partial line — ignore. The stream-map handles malformed input. + } +} + +/** + * Resolve the `grok` binary path. The bootstrap installs `@xai-official/grok` + * into the bootstrap dir's node_modules, exposing `./node_modules/.bin/grok`. + * Fall back to bare `grok` (PATH) when that shim is absent. + */ +function resolveGrokBinary(dir: string): string { + const local = `${dir}/node_modules/.bin/grok`; + return existsSync(local) ? local : 'grok'; +} + +function parseArgs(rawArgs: string[]): { + workdir?: string; + bridgeStateDir?: string; + bootstrapDir?: string; +} { + const out: { + workdir?: string; + bridgeStateDir?: string; + bootstrapDir?: string; + } = {}; + for (let i = 0; i < rawArgs.length; i++) { + if (rawArgs[i] === '--workdir' && i + 1 < rawArgs.length) { + out.workdir = rawArgs[++i]; + } else if (rawArgs[i] === '--bridge-state-dir' && i + 1 < rawArgs.length) { + out.bridgeStateDir = rawArgs[++i]; + } else if (rawArgs[i] === '--bootstrap-dir' && i + 1 < rawArgs.length) { + out.bootstrapDir = rawArgs[++i]; + } + } + return out; +} + +function serialiseError(err: unknown): unknown { + if (err instanceof Error) { + return { name: err.name, message: err.message, stack: err.stack }; + } + return err; +} + +function emitFatal(message: string): never { + stdout.write(JSON.stringify({ type: 'bridge-fatal', message }) + '\n'); + process.exit(1); +} diff --git a/packages/harness-grok-build/src/grok-build-auth.test.ts b/packages/harness-grok-build/src/grok-build-auth.test.ts index c5a2453e7b96..da662d9aac42 100644 --- a/packages/harness-grok-build/src/grok-build-auth.test.ts +++ b/packages/harness-grok-build/src/grok-build-auth.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { resolveGrokBuildEnv } from './grok-build-auth'; +import { resolveGrokBuildEnv, toGrokCliEnv } from './grok-build-auth'; describe('resolveGrokBuildEnv', () => { it('uses explicit xai api key', () => { @@ -36,3 +36,32 @@ describe('resolveGrokBuildEnv', () => { expect(env.XAI_BASE_URL).toBe('https://x'); }); }); + +describe('toGrokCliEnv', () => { + it('maps direct xai auth to XAI_API_KEY', () => { + const resolved = resolveGrokBuildEnv({ xai: { apiKey: 'sk-direct' } }, {}); + const cliEnv = toGrokCliEnv(resolved); + expect(cliEnv.XAI_API_KEY).toBe('sk-direct'); + expect(cliEnv.GROK_CODE_XAI_API_KEY).toBeUndefined(); + expect(cliEnv.GROK_MODELS_BASE_URL).toBeUndefined(); + }); + + it('forwards a direct custom base url', () => { + const cliEnv = toGrokCliEnv( + resolveGrokBuildEnv({ xai: { apiKey: 'k', baseUrl: 'https://x' } }, {}), + ); + expect(cliEnv.XAI_BASE_URL).toBe('https://x'); + }); + + it('maps gateway auth to GROK_CODE_XAI_API_KEY + GROK_MODELS_BASE_URL', () => { + const resolved = resolveGrokBuildEnv( + { gateway: { apiKey: 'gw-key', baseUrl: 'https://gateway.example/v1' } }, + {}, + ); + const cliEnv = toGrokCliEnv(resolved); + expect(cliEnv.GROK_CODE_XAI_API_KEY).toBe('gw-key'); + expect(cliEnv.GROK_MODELS_BASE_URL).toBe('https://gateway.example/v1'); + // The direct xAI var must not leak when routing through the gateway. + expect(cliEnv.XAI_API_KEY).toBeUndefined(); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts index 98e738a53f10..9b13e7045c85 100644 --- a/packages/harness-grok-build/src/grok-build-auth.ts +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -38,6 +38,38 @@ export function resolveGrokBuildEnv( return pickXai({}, processEnv); } +/** + * Map the generic resolved auth blob (from {@link resolveGrokBuildEnv}) onto the + * concrete environment variables the `grok` CLI actually reads. The CLI does + * NOT read `XAI_API_KEY` / `AI_GATEWAY_*` directly, so this translation is + * required before spawning. + * + * Decision: gateway-vs-direct is keyed off the presence of `AI_GATEWAY_API_KEY` + * in the resolved blob (the gateway branch of `resolveGrokBuildEnv` always sets + * it). The two shapes: + * - Direct xAI: `XAI_API_KEY=` (and pass model id `grok-build-0.1`). + * - Gateway: `GROK_MODELS_BASE_URL=` + + * `GROK_CODE_XAI_API_KEY=` (and pass model id + * `xai/grok-build-0.1`). + */ +export function toGrokCliEnv( + resolved: Record, +): Record { + const isGateway = resolved.AI_GATEWAY_API_KEY != null; + if (isGateway) { + const env: Record = {}; + const key = resolved.AI_GATEWAY_API_KEY; + const baseUrl = resolved.AI_GATEWAY_BASE_URL ?? resolved.XAI_BASE_URL; + if (key) env.GROK_CODE_XAI_API_KEY = key; + if (baseUrl) env.GROK_MODELS_BASE_URL = baseUrl; + return env; + } + const env: Record = {}; + if (resolved.XAI_API_KEY) env.XAI_API_KEY = resolved.XAI_API_KEY; + if (resolved.XAI_BASE_URL) env.XAI_BASE_URL = resolved.XAI_BASE_URL; + return env; +} + function pickXai( explicit: NonNullable, processEnv: Record, diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts index 9c02fb3b6f11..54a1737fedb2 100644 --- a/packages/harness-grok-build/src/grok-build-harness.test.ts +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -1,10 +1,35 @@ -import type * as NodeFsPromises from 'node:fs/promises'; -import { describe, expect, it, vi } from 'vitest'; import { - createGrokBuild, - GROK_BUILD_BUILTIN_TOOLS, - toCommonName, -} from './grok-build-harness'; + HarnessCapabilityUnsupportedError, + type HarnessV1NetworkSandboxSession, +} from '@ai-sdk/harness'; +import type * as HarnessUtils from '@ai-sdk/harness/utils'; +import type * as NodeFsPromises from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const sentMessages: Array> = []; + +vi.mock('@ai-sdk/harness/utils', async importOriginal => { + const actual = await importOriginal(); + class FakeSandboxChannel { + async open(): Promise {} + on(): () => void { + return () => {}; + } + onClose(): void {} + send(msg: Record): void { + sentMessages.push(msg); + } + beginClose(): void {} + isClosed(): boolean { + return false; + } + close(): void {} + async suspend(): Promise { + return 0; + } + } + return { ...actual, SandboxChannel: FakeSandboxChannel }; +}); vi.mock('node:fs/promises', async importOriginal => { const actual = await importOriginal(); @@ -15,11 +40,73 @@ vi.mock('node:fs/promises', async importOriginal => { if (p.endsWith('/bridge/index.mjs')) return '// mock bridge\n'; if (p.endsWith('/bridge/package.json')) return '{"name":"mock"}'; if (p.endsWith('/bridge/pnpm-lock.yaml')) return 'lockfileVersion: 9\n'; - return actual.readFile(input as any, ...(rest as any[])); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (actual.readFile as any)(input, ...rest); }), }; }); +// eslint-disable-next-line import/first +import { + createGrokBuild, + GROK_BUILD_BUILTIN_TOOLS, + toCommonName, +} from './grok-build-harness'; + +function textStream(text: string): ReadableStream { + return new ReadableStream({ + start(controller) { + if (text.length > 0) { + controller.enqueue(new TextEncoder().encode(text)); + } + controller.close(); + }, + }); +} + +function fakeSandbox({ + spawnCalls, + runs, +}: { + spawnCalls: Array<{ command: string; env: Record }>; + runs: string[]; +}): HarnessV1NetworkSandboxSession { + const session = { + run: async ({ command }: { command: string }) => { + runs.push(command); + return { exitCode: 0, stdout: '', stderr: '' }; + }, + readTextFile: async () => null, + writeTextFile: async () => {}, + spawn: async ({ + command, + env, + }: { + command: string; + env: Record; + }) => { + spawnCalls.push({ command, env }); + return { + stdout: textStream('{"type":"bridge-ready","port":4319}\n'), + stderr: textStream(''), + kill: async () => {}, + wait: async () => ({ exitCode: 0 }), + }; + }, + }; + return { + id: 'test-sandbox', + defaultWorkingDirectory: '/vercel/sandbox', + restricted: () => session, + ports: [4319], + async getPortUrl() { + return 'ws://127.0.0.1:4319'; + }, + async stop() {}, + ...session, + } as unknown as HarnessV1NetworkSandboxSession; +} + describe('grok-build builtin tools', () => { it('exposes common tool names', () => { expect(Object.keys(GROK_BUILD_BUILTIN_TOOLS)).toEqual( @@ -49,3 +136,176 @@ describe('grok-build bootstrap', () => { expect(bootstrap.commands.some(c => c.command.includes('pnpm'))).toBe(true); }); }); + +describe('grok-build doStart', () => { + beforeEach(() => { + sentMessages.length = 0; + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('rejects built-in permission modes other than allow-all', async () => { + const harness = createGrokBuild(); + await expect( + harness.doStart({ + sessionId: 's1', + sandboxSession: {} as HarnessV1NetworkSandboxSession, + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-edits', + }), + ).rejects.toBeInstanceOf(HarnessCapabilityUnsupportedError); + }); + + it('throws when the network sandbox exposes no ports', async () => { + const harness = createGrokBuild(); + const sandboxSession = { + id: 'test-sandbox', + defaultWorkingDirectory: '/vercel/sandbox', + restricted: () => ({}) as never, + ports: [] as ReadonlyArray, + async getPortUrl() { + return ''; + }, + async stop() {}, + } as unknown as HarnessV1NetworkSandboxSession; + await expect( + harness.doStart({ + sessionId: 's1', + sandboxSession, + sessionWorkDir: '/vercel/sandbox/grok-s1', + }), + ).rejects.toBeInstanceOf(HarnessCapabilityUnsupportedError); + }); + + it('spawns the bridge with the workdir and mapped direct grok env', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk-direct' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + + expect(spawnCalls).toHaveLength(1); + expect(spawnCalls[0].command).toContain( + 'node /tmp/harness/grok-build/bridge.mjs --workdir /vercel/sandbox/grok-s1', + ); + // Mapped direct-xai env var is forwarded to the bridge process. + expect(spawnCalls[0].env.XAI_API_KEY).toBe('sk-direct'); + expect(spawnCalls[0].env.BRIDGE_WS_PORT).toBe('4319'); + expect(spawnCalls[0].env.BRIDGE_CHANNEL_TOKEN).toBeTruthy(); + // Direct route pins the bare model id. + expect(session.modelId).toBe('grok-build-0.1'); + expect(session.isResume).toBe(false); + }); + + it('uses the gateway-prefixed model id and mapped gateway env', async () => { + const harness = createGrokBuild({ + auth: { gateway: { apiKey: 'gw-key', baseUrl: 'https://gw/v1' } }, + }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + + expect(spawnCalls[0].env.GROK_CODE_XAI_API_KEY).toBe('gw-key'); + expect(spawnCalls[0].env.GROK_MODELS_BASE_URL).toBe('https://gw/v1'); + expect(spawnCalls[0].env.XAI_API_KEY).toBeUndefined(); + expect(session.modelId).toBe('xai/grok-build-0.1'); + }); + + it('sends a start message on doPromptTurn carrying the model', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + + await session.doPromptTurn({ prompt: 'hello', emit: () => {} }); + const start = sentMessages.find(m => m.type === 'start'); + expect(start).toMatchObject({ + type: 'start', + prompt: 'hello', + model: 'grok-build-0.1', + }); + }); + + it('sends a continuation start message with continue:true on doContinueTurn', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + + await session.doContinueTurn({ emit: () => {} }); + const start = sentMessages.find(m => m.type === 'start'); + expect(start).toMatchObject({ type: 'start', continue: true }); + }); + + it('reports isResume when resumeFrom carries a grok session id', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + resumeFrom: { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { sessionId: 'grok-sess-123' }, + }, + }); + expect(session.isResume).toBe(true); + }); + + it('rejects manual compaction', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + await expect(session.doCompact!()).rejects.toBeInstanceOf( + HarnessCapabilityUnsupportedError, + ); + }); +}); diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 2292e1ce5476..1f24e9b62cc9 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -1,14 +1,41 @@ +import { randomBytes } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { commonTool, + HarnessCapabilityUnsupportedError, + harnessV1DiagnosticFromBridgeFrame, type HarnessV1, type HarnessV1Bootstrap, type HarnessV1BuiltinTool, type HarnessV1BuiltinToolName, + type HarnessV1DebugConfig, + type HarnessV1NetworkSandboxSession, + type HarnessV1PermissionMode, + type HarnessV1ResumeSessionState, + type HarnessV1Session, + type HarnessV1StreamPart, } from '@ai-sdk/harness'; +import { + markBridgeStarting, + SandboxChannel, + waitForBridgeReady, +} from '@ai-sdk/harness/utils'; +import { type Experimental_SandboxProcess } from '@ai-sdk/provider-utils'; +import { WebSocket } from 'ws'; import { z } from 'zod'; -import { type GrokBuildAuthOptions } from './grok-build-auth'; +import { + resolveGrokBuildEnv, + toGrokCliEnv, + type GrokBuildAuthOptions, +} from './grok-build-auth'; +import { + outboundMessageSchema, + type InboundMessage, + type OutboundMessage, +} from './grok-build-bridge-protocol'; + +type GrokBuildChannel = SandboxChannel; /* * Native tool name → common harness name mapping. @@ -122,13 +149,40 @@ export const GROK_BUILD_BUILTIN_TOOLS = { const BOOTSTRAP_DIR = '/tmp/harness/grok-build'; +/* + * The grok model id the adapter pins when the consumer configures none. The + * direct (xAI) and gateway routes use different ids: direct uses the bare + * `grok-build-0.1`, while the gateway requires the `xai/` prefix. + */ +const DEFAULT_GROK_MODEL_DIRECT = 'grok-build-0.1'; +const DEFAULT_GROK_MODEL_GATEWAY = 'xai/grok-build-0.1'; + export type GrokBuildHarnessSettings = { readonly model?: string; readonly planMode?: boolean; readonly auth?: GrokBuildAuthOptions; readonly port?: number; + /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */ + readonly startupTimeoutMs?: number; }; +/** + * Adapter-specific lifecycle `data` payload. `sessionId` is the grok CLI + * session id (from the terminal `end` event) usable for `-r/--resume`; `bridge` + * carries live coordinates for a cross-process attach. + */ +const lifecycleStateSchema = z.object({ + sessionId: z.string().optional(), + bridge: z + .object({ + port: z.number(), + token: z.string(), + lastSeenEventId: z.number(), + sandboxId: z.string().optional(), + }) + .optional(), +}); + async function readBridgeAsset(name: string): Promise { const candidates = [ new URL(`./bridge/${name}`, import.meta.url), @@ -148,8 +202,7 @@ async function readBridgeAsset(name: string): Promise { } export function createGrokBuild( - // Renamed to `settings` once doStart consumes it (turn-driver task). - _settings: GrokBuildHarnessSettings = {}, + settings: GrokBuildHarnessSettings = {}, ): HarnessV1 { // Per-instance cache: bridge assets are static, but keeping this in the // factory closure (rather than module scope) avoids leaking state across @@ -160,6 +213,7 @@ export function createGrokBuild( harnessId: 'grok-build', builtinTools: GROK_BUILD_BUILTIN_TOOLS, supportsBuiltinToolApprovals: false, + lifecycleStateSchema, getBootstrap: async () => { if (cachedBootstrap != null) return cachedBootstrap; const [pkg, lock, bridge] = await Promise.all([ @@ -187,8 +241,530 @@ export function createGrokBuild( }; return cachedBootstrap; }, - doStart: async () => { - throw new Error('not implemented yet'); + doStart: async startOpts => { + if ( + startOpts.permissionMode != null && + startOpts.permissionMode !== 'allow-all' + ) { + throw new HarnessCapabilityUnsupportedError({ + message: + "Harness 'grok-build' does not support built-in tool approval requests; use permissionMode: 'allow-all'. The grok CLI runs with --always-approve and executes tools itself.", + harnessId: 'grok-build', + }); + } + + const sandboxSession = startOpts.sandboxSession; + const session = sandboxSession.restricted(); + const sandboxId = sandboxSession.id; + const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom; + const isResume = lifecycleState != null; + const resumeData = + isResume && typeof lifecycleState?.data === 'object' + ? (lifecycleState.data as { + sessionId?: unknown; + }) + : undefined; + const resumeSessionId = + typeof resumeData?.sessionId === 'string' && + resumeData.sessionId.length > 0 + ? resumeData.sessionId + : undefined; + + const workDir = startOpts.sessionWorkDir; + const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`; + const bridgeStateDir = `${sessionDataDir}/bridge`; + const timeoutMs = settings.startupTimeoutMs ?? 120_000; + + // Normalize each forwarded bridge diagnostics frame into the general + // `HarnessV1Diagnostic` and report it. + const report = startOpts.observability?.report; + const onDiagnostic = report + ? (frame: Parameters[0]) => + report( + harnessV1DiagnosticFromBridgeFrame(frame, { + sessionId: startOpts.sessionId, + timestamp: Date.now(), + }), + ) + : undefined; + + // Resolve auth, then translate the generic blob into the concrete env + // vars the grok CLI reads, and pick the matching model id (gateway needs + // the `xai/` prefix). + const resolvedAuth = resolveGrokBuildEnv(settings.auth); + const grokEnv = toGrokCliEnv(resolvedAuth); + const isGateway = resolvedAuth.AI_GATEWAY_API_KEY != null; + const model = + settings.model ?? + (isGateway ? DEFAULT_GROK_MODEL_GATEWAY : DEFAULT_GROK_MODEL_DIRECT); + + const port = resolveBridgePort(sandboxSession, settings.port); + const token = randomBytes(32).toString('hex'); + const env = { + ...grokEnv, + BRIDGE_CHANNEL_TOKEN: token, + BRIDGE_WS_PORT: String(port), + }; + + await session.run({ + command: `mkdir -p ${workDir} ${bridgeStateDir}`, + abortSignal: startOpts.abortSignal, + }); + + await markBridgeStarting({ + sandbox: session, + bridgeStateDir, + bridgeType: 'grok-build', + abortSignal: startOpts.abortSignal, + }); + + const proc = await session.spawn({ + command: `node ${BOOTSTRAP_DIR}/bridge.mjs --workdir ${workDir} --bridge-state-dir ${bridgeStateDir} --bootstrap-dir ${BOOTSTRAP_DIR}`, + env, + abortSignal: startOpts.abortSignal, + }); + + const { port: boundPort } = await waitForBridgeReady({ + proc, + sandbox: session, + bridgeStateDir, + bridgeType: 'grok-build', + timeoutMs, + abortSignal: startOpts.abortSignal, + createTimeoutError: () => + new Error('grok-build bridge did not become ready in time.'), + createExitError: () => + new Error('grok-build bridge exited before becoming ready.'), + }); + void drainRest(proc.stdout); + void forwardBridgeStderr(proc.stderr); + + const wsUrl = + (await sandboxSession.getPortUrl({ + port: boundPort, + protocol: 'ws', + })) + `?agent_bridge_token=${encodeURIComponent(token)}`; + + const channel: GrokBuildChannel = new SandboxChannel({ + connect: () => openWebSocket(wsUrl), + outboundSchema: outboundMessageSchema, + onDiagnostic, + }); + await channel.open(); + + return createSession({ + sessionId: startOpts.sessionId, + channel, + proc, + model, + isResume, + bridgePort: boundPort, + bridgeToken: token, + sandboxId, + debug: startOpts.observability?.debug, + permissionMode: startOpts.permissionMode, + resumeGrokSessionId: resumeSessionId, + }); + }, + }; +} + +function resolveBridgePort( + sandboxSession: HarnessV1NetworkSandboxSession, + override: number | undefined, +): number { + if (override !== undefined) return override; + if (sandboxSession.ports.length > 0) return sandboxSession.ports[0]; + throw new HarnessCapabilityUnsupportedError({ + harnessId: 'grok-build', + message: + 'The grok-build harness needs a TCP port exposed by the sandbox. ' + + 'Create the sandbox with `ports: []` or pass `createGrokBuild({ port })`.', + }); +} + +function openWebSocket(url: string): Promise { + return new Promise((resolve, reject) => { + const ws = new WebSocket(url); + const onOpen = () => { + ws.off('error', onError); + resolve(ws); + }; + const onError = (err: Error) => { + ws.off('open', onOpen); + reject(err); + }; + ws.once('open', onOpen); + ws.once('error', onError); + }); +} + +async function forwardBridgeStderr( + stream: ReadableStream, +): Promise { + try { + const reader = stream.pipeThrough(new TextDecoderStream()).getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) return; + if (value) { + const trimmed = value.endsWith('\n') ? value.slice(0, -1) : value; + if (trimmed.length > 0) { + // eslint-disable-next-line no-console + console.log(`[bridge stderr] ${trimmed}`); + } + } + } + } catch { + // Reader errors are non-fatal — best-effort diagnostic only. + } +} + +async function drainRest(stream: ReadableStream): Promise { + try { + const reader = stream.pipeThrough(new TextDecoderStream()).getReader(); + while (true) { + const { done } = await reader.read(); + if (done) return; + } + } catch {} +} + +function createSession({ + sessionId, + channel, + proc, + model, + isResume, + bridgePort, + bridgeToken, + sandboxId, + debug, + permissionMode, + resumeGrokSessionId, +}: { + sessionId: string; + channel: GrokBuildChannel; + /** Undefined on `attach` — the live bridge was spawned by another process. */ + proc: Experimental_SandboxProcess | undefined; + model: string | undefined; + isResume: boolean; + bridgePort: number; + bridgeToken: string; + sandboxId: string; + debug: HarnessV1DebugConfig | undefined; + permissionMode: HarnessV1PermissionMode | undefined; + resumeGrokSessionId: string | undefined; +}): HarnessV1Session { + void debug; + void permissionMode; + let stopped = false; + let stopPromise: Promise | undefined; + + /* + * Latest grok CLI session id, cached from the bridge's `bridge-detach` + * payload-bearing announcements is not available pre-detach; seed from + * lifecycle state so `doDetach`/`doStop` can include an id even before this + * process has finished a turn. + */ + let latestGrokSessionId = resumeGrokSessionId; + + /* + * Wire the channel into one turn's worth of events and return the control + * surface. Shared by `doPromptTurn` and `doContinueTurn` (which differ only + * in the `start` message they send afterwards). + */ + const wireTurn = (turnOpts: { + emit: (event: HarnessV1StreamPart) => void; + abortSignal?: AbortSignal; + }): { done: Promise } => { + let pendingResolve: (() => void) | undefined; + let pendingReject: ((err: unknown) => void) | undefined; + const done = new Promise((resolve, reject) => { + pendingResolve = resolve; + pendingReject = reject; + }); + + const unsubs: Array<() => void> = []; + const forward = (event: HarnessV1StreamPart) => { + try { + turnOpts.emit(event); + } catch {} + }; + + const eventTypes = [ + 'stream-start', + 'text-start', + 'text-delta', + 'text-end', + 'reasoning-start', + 'reasoning-delta', + 'reasoning-end', + 'tool-call', + 'tool-approval-request', + 'tool-result', + 'file-change', + 'finish-step', + 'raw', + ] as const; + let isSettled = false; + const settleSuccess = () => { + if (isSettled) return; + isSettled = true; + for (const u of unsubs) u(); + pendingResolve!(); + }; + const settleError = (err: unknown) => { + if (isSettled) return; + isSettled = true; + for (const u of unsubs) u(); + pendingReject!(err); + }; + + for (const type of eventTypes) { + unsubs.push(channel.on(type, msg => forward(msg))); + } + unsubs.push( + channel.on('finish', msg => { + forward(msg); + settleSuccess(); + }), + ); + unsubs.push( + channel.on('error', msg => { + forward(msg); + settleError(msg.error); + }), + ); + + const onClose = (_code?: number, reason?: string) => { + if (isSettled) return; + if (reason === 'suspended') { + settleSuccess(); + return; + } + settleError( + new Error('grok-build bridge closed before the turn finished.'), + ); + }; + channel.onClose(onClose); + + const onAbort = () => { + if (isSettled) return; + try { + channel.send({ type: 'abort' }); + } catch {} + settleError( + turnOpts.abortSignal?.reason ?? + new DOMException('Aborted', 'AbortError'), + ); + }; + if (turnOpts.abortSignal) { + if (turnOpts.abortSignal.aborted) { + onAbort(); + } else { + turnOpts.abortSignal.addEventListener('abort', onAbort, { + once: true, + }); + } + } + + return { done }; + }; + + /* + * Tools execute inside grok via `--always-approve`, so the host never + * dispatches tools in this mode: `submitToolResult` / `submitToolApproval` + * are unsupported no-ops that match the `HarnessV1PromptControl` interface. + */ + const unsupportedToolControl = { + submitToolResult: async () => { + throw new HarnessCapabilityUnsupportedError({ + harnessId: 'grok-build', + message: + 'The grok-build harness executes tools inside the CLI (--always-approve); host tool results are not accepted.', + }); + }, + submitToolApproval: async () => { + throw new HarnessCapabilityUnsupportedError({ + harnessId: 'grok-build', + message: + 'The grok-build harness executes tools inside the CLI (--always-approve); host tool approvals are not accepted.', + }); }, }; + + return { + sessionId, + isResume, + modelId: model, + doPromptTurn: async promptOpts => { + const { done } = wireTurn({ + emit: promptOpts.emit, + abortSignal: promptOpts.abortSignal, + }); + channel.send({ + type: 'start', + prompt: extractUserText(promptOpts.prompt), + ...(model ? { model } : {}), + }); + return { ...unsupportedToolControl, done }; + }, + doContinueTurn: async continueOpts => { + const { done } = wireTurn({ + emit: continueOpts.emit, + abortSignal: continueOpts.abortSignal, + }); + // `doContinueTurn` carries no prompt; the grok CLI requires `-p`, so send + // a continuation nudge alongside `-c` (which resumes the prior thread in + // the workdir). Mirrors the codex adapter. + channel.send({ + type: 'start', + prompt: 'Continue.', + ...(model ? { model } : {}), + continue: true, + }); + return { ...unsupportedToolControl, done }; + }, + doCompact: async () => { + throw new HarnessCapabilityUnsupportedError({ + message: "Harness 'grok-build' does not support manual compaction.", + harnessId: 'grok-build', + }); + }, + doDetach: async () => { + if (stopped) { + throw new Error( + `grok-build session ${sessionId} is already stopped; cannot detach.`, + ); + } + stopped = true; + const lastSeenEventId = await channel.suspend(); + return { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { + ...(latestGrokSessionId ? { sessionId: latestGrokSessionId } : {}), + bridge: { + port: bridgePort, + token: bridgeToken, + lastSeenEventId, + sandboxId, + }, + }, + } satisfies HarnessV1ResumeSessionState; + }, + doStop: async () => { + if (stopped) { + throw new Error( + `grok-build session ${sessionId} is already stopped; cannot stop.`, + ); + } + stopped = true; + channel.beginClose(); + let stopTimer: ReturnType | undefined; + try { + if (proc) { + await Promise.race([ + proc.wait(), + new Promise(resolve => { + stopTimer = setTimeout(resolve, 5000); + stopTimer.unref?.(); + }), + ]); + } + } finally { + if (stopTimer) clearTimeout(stopTimer); + try { + await proc?.kill(); + } catch {} + channel.close(); + } + return { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: latestGrokSessionId ? { sessionId: latestGrokSessionId } : {}, + } satisfies HarnessV1ResumeSessionState; + }, + doSuspendTurn: async () => { + if (stopped) { + throw new Error( + `grok-build session ${sessionId} is stopped; cannot suspend.`, + ); + } + stopped = true; + const lastSeenEventId = await channel.suspend(); + return { + type: 'continue-turn', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { + ...(latestGrokSessionId ? { sessionId: latestGrokSessionId } : {}), + bridge: { + port: bridgePort, + token: bridgeToken, + lastSeenEventId, + sandboxId, + }, + }, + }; + }, + doDestroy: async () => { + if (stopped) return stopPromise; + stopped = true; + stopPromise = (async () => { + channel.beginClose(); + try { + if (!channel.isClosed()) { + channel.send({ type: 'shutdown' }); + } + } catch {} + let stopTimer: ReturnType | undefined; + try { + if (proc) { + await Promise.race([ + proc.wait(), + new Promise(resolve => { + stopTimer = setTimeout(resolve, 5000); + stopTimer.unref?.(); + }), + ]); + } + } finally { + if (stopTimer) clearTimeout(stopTimer); + try { + await proc?.kill(); + } catch {} + channel.close(); + } + })(); + return stopPromise; + }, + }; +} + +/* + * Reduce a `HarnessV1Prompt` to the plain user text the bridge passes to the + * grok CLI via `-p`. File and image parts are not yet supported — throw rather + * than silently drop them. + */ +function extractUserText( + prompt: Parameters[0]['prompt'], +): string { + if (typeof prompt === 'string') return prompt; + const { content } = prompt; + if (typeof content === 'string') return content; + const parts: string[] = []; + for (const part of content) { + if (part.type !== 'text') { + throw new HarnessCapabilityUnsupportedError({ + harnessId: 'grok-build', + message: `The grok-build harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`, + }); + } + parts.push(part.text); + } + return parts.join('\n\n'); } diff --git a/packages/harness-grok-build/src/index.ts b/packages/harness-grok-build/src/index.ts index aed3b137e46b..72c53b682376 100644 --- a/packages/harness-grok-build/src/index.ts +++ b/packages/harness-grok-build/src/index.ts @@ -1 +1,12 @@ -export const grokBuildPlaceholder = true; +import { createGrokBuild } from './grok-build-harness'; + +/** + * Default `grok-build` harness instance with no overrides — suitable for the + * common case where the underlying `grok` CLI's defaults are fine. + * Equivalent to `createGrokBuild()`. + */ +export const grokBuild = createGrokBuild(); + +export { createGrokBuild } from './grok-build-harness'; +export type { GrokBuildHarnessSettings } from './grok-build-harness'; +export type { GrokBuildAuthOptions } from './grok-build-auth'; From ad747c6c415782556033172ce687ec821d628dc8 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 11:45:00 -0700 Subject: [PATCH 10/23] add local generate-text smoke test --- examples/ai-functions/package.json | 1 + .../harness-agent/grok-build/generate-text.ts | 33 +++++++++++++++++++ pnpm-lock.yaml | 3 ++ 3 files changed, 37 insertions(+) create mode 100644 examples/ai-functions/src/harness-agent/grok-build/generate-text.ts diff --git a/examples/ai-functions/package.json b/examples/ai-functions/package.json index 97f2a111323e..3ffc3ec810a5 100644 --- a/examples/ai-functions/package.json +++ b/examples/ai-functions/package.json @@ -30,6 +30,7 @@ "@ai-sdk/harness": "workspace:*", "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", + "@ai-sdk/harness-grok-build": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/huggingface": "workspace:*", "@ai-sdk/hume": "workspace:*", diff --git a/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts b/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts new file mode 100644 index 000000000000..d3b311e981ef --- /dev/null +++ b/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts @@ -0,0 +1,33 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { run } from '../../lib/run'; + +// Local end-to-end smoke test. Uses the just-bash sandbox so it runs on the +// host (no remote sandbox). Requires XAI_API_KEY (or AI Gateway env) in +// examples/ai-functions/.env. The bootstrap installs the `grok` CLI under +// /tmp/harness/grok-build and the bridge drives it in streaming-json mode. +run(async () => { + const agent = new HarnessAgent({ + harness: grokBuild, + sandbox: createJustBashSandbox(), + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + const result = await agent.generate({ + session, + prompt: 'In one sentence, what is the capital of France?', + }); + console.log('text:', result.text); + console.log('finishReason:', result.finishReason); + console.log('usage:', result.usage); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + process.exit(exitCode); + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1cfec5f7836e..72cc836adcf1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -307,6 +307,9 @@ importers: '@ai-sdk/harness-codex': specifier: workspace:* version: link:../../packages/harness-codex + '@ai-sdk/harness-grok-build': + specifier: workspace:* + version: link:../../packages/harness-grok-build '@ai-sdk/harness-pi': specifier: workspace:* version: link:../../packages/harness-pi From 692088b61a3277c2827d7f72c604d102067dc0f0 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 11:49:27 -0700 Subject: [PATCH 11/23] adding vercel sandbox to smoke test --- .../harness-agent/grok-build/generate-text.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts b/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts index d3b311e981ef..f7697535b6b4 100644 --- a/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts +++ b/examples/ai-functions/src/harness-agent/grok-build/generate-text.ts @@ -1,16 +1,21 @@ import { HarnessAgent } from '@ai-sdk/harness/agent'; import { grokBuild } from '@ai-sdk/harness-grok-build'; -import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; import { run } from '../../lib/run'; -// Local end-to-end smoke test. Uses the just-bash sandbox so it runs on the -// host (no remote sandbox). Requires XAI_API_KEY (or AI Gateway env) in -// examples/ai-functions/.env. The bootstrap installs the `grok` CLI under -// /tmp/harness/grok-build and the bridge drives it in streaming-json mode. +// End-to-end smoke test against the Vercel Sandbox (required: grok-build is +// bridge-backed and needs a port, which the local just-bash sandbox cannot +// expose). Requires Vercel Sandbox credentials (OIDC token / `vercel` auth) +// and XAI_API_KEY (or AI Gateway env) in examples/ai-functions/.env. run(async () => { + const sandbox = createVercelSandbox({ + runtime: 'node24', + ports: [4000], + timeout: 10 * 60 * 1000, + }); const agent = new HarnessAgent({ harness: grokBuild, - sandbox: createJustBashSandbox(), + sandbox, }); let exitCode = 0; From a4e6cf61fab2accbf15774585e1a5f9adea8eb27 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 13:27:09 -0700 Subject: [PATCH 12/23] setting changeset to major --- .changeset/grok-build-turn-driver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/grok-build-turn-driver.md b/.changeset/grok-build-turn-driver.md index a922e61d7241..607264285ddc 100644 --- a/.changeset/grok-build-turn-driver.md +++ b/.changeset/grok-build-turn-driver.md @@ -1,5 +1,5 @@ --- -'@ai-sdk/harness-grok-build': patch +'@ai-sdk/harness-grok-build': major --- feat(harness-grok-build): implement bridge turn driver and doStart From 0c67b1abd9923302c1d1b205c1d980b0536fb2a7 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 13:27:16 -0700 Subject: [PATCH 13/23] cutting --- .changeset/grok-build-turn-driver.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.changeset/grok-build-turn-driver.md b/.changeset/grok-build-turn-driver.md index 607264285ddc..9acff386def9 100644 --- a/.changeset/grok-build-turn-driver.md +++ b/.changeset/grok-build-turn-driver.md @@ -2,6 +2,4 @@ '@ai-sdk/harness-grok-build': major --- -feat(harness-grok-build): implement bridge turn driver and doStart - -The grok-build harness now runs a real turn end-to-end: it spawns the `grok` CLI inside the sandbox with `--output-format streaming-json --always-approve`, streams stdout through `mapStreamLine`, and emits `HarnessV1StreamPart` events. Adds `toGrokCliEnv` to map resolved auth onto the CLI's real env vars (direct `XAI_API_KEY` vs gateway `GROK_CODE_XAI_API_KEY` + `GROK_MODELS_BASE_URL`) and a `createSession` implementation covering prompt/continue turns, detach/stop/suspend lifecycle, and destroy. +feat(harness-grok-build): implement bridge turn driver and doStart \ No newline at end of file From 604a37296cc703212a4aee4add79f7b9704694c2 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 13:35:01 -0700 Subject: [PATCH 14/23] adding robustness + cleaning --- .../src/bridge/grok-build-path.test.ts | 29 +++++++ .../src/bridge/grok-build-path.ts | 18 ++++ .../harness-grok-build/src/bridge/index.ts | 34 +++----- .../harness-grok-build/src/grok-build-auth.ts | 19 +---- .../src/grok-build-bridge-protocol.ts | 12 +-- .../src/grok-build-harness.test.ts | 2 +- .../src/grok-build-harness.ts | 82 +++++-------------- .../src/grok-build-stream-map.ts | 61 +------------- 8 files changed, 86 insertions(+), 171 deletions(-) create mode 100644 packages/harness-grok-build/src/bridge/grok-build-path.test.ts create mode 100644 packages/harness-grok-build/src/bridge/grok-build-path.ts diff --git a/packages/harness-grok-build/src/bridge/grok-build-path.test.ts b/packages/harness-grok-build/src/bridge/grok-build-path.test.ts new file mode 100644 index 000000000000..027687a93b71 --- /dev/null +++ b/packages/harness-grok-build/src/bridge/grok-build-path.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; +import { prependGrokBuildBinToPath } from './grok-build-path'; + +describe('prependGrokBuildBinToPath', () => { + it('prepends the bridge node_modules bin directory', () => { + const env = { PATH: '/usr/bin:/bin' }; + + prependGrokBuildBinToPath({ + bootstrapDir: '/tmp/harness/grok-build', + env, + }); + + expect(env.PATH).toBe( + '/tmp/harness/grok-build/node_modules/.bin:/usr/bin:/bin', + ); + }); + + it('keeps a usable system path fallback when PATH is absent', () => { + const env: { PATH?: string } = {}; + + prependGrokBuildBinToPath({ + bootstrapDir: '/tmp/harness/grok-build', + env, + }); + + expect(env.PATH).toContain('/tmp/harness/grok-build/node_modules/.bin:'); + expect(env.PATH).toContain('/usr/bin'); + }); +}); diff --git a/packages/harness-grok-build/src/bridge/grok-build-path.ts b/packages/harness-grok-build/src/bridge/grok-build-path.ts new file mode 100644 index 000000000000..62287826196d --- /dev/null +++ b/packages/harness-grok-build/src/bridge/grok-build-path.ts @@ -0,0 +1,18 @@ +import path from 'node:path'; + +const fallbackPath = + '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'; + +// Prepend the bootstrap's node_modules/.bin to PATH so the installed `grok` wins. +export function prependGrokBuildBinToPath({ + bootstrapDir, + env, +}: { + bootstrapDir: string; + env: NodeJS.ProcessEnv; +}): void { + env.PATH = [ + path.join(bootstrapDir, 'node_modules', '.bin'), + env.PATH || fallbackPath, + ].join(path.delimiter); +} diff --git a/packages/harness-grok-build/src/bridge/index.ts b/packages/harness-grok-build/src/bridge/index.ts index 467d90b50395..bd76876c7dd0 100644 --- a/packages/harness-grok-build/src/bridge/index.ts +++ b/packages/harness-grok-build/src/bridge/index.ts @@ -1,14 +1,6 @@ -// Long-running process that runs alongside the `grok` CLI in the sandbox. -// The generic transport — WebSocket server, token auth, single-flight -// reconnect, the in-memory event log + `seq`, resume replay, and the -// lifecycle/meta files — lives in the shared `@ai-sdk/harness/bridge` runtime. -// This file supplies only the Grok-specific turn driver. -// -// Grok is CLI-driven: a fresh `grok -p --output-format streaming-json` -// child is spawned per turn. Because the CLI runs with `--always-approve`, all -// tools execute *inside* grok in the sandbox — there is NO host tool dispatch in -// this mode (no relay, no MCP shim). `turn.requestToolResult` / -// `requestToolApproval` are therefore never used here. +// Grok-specific turn driver for the shared @ai-sdk/harness/bridge runtime. +// Spawns a `grok -p ... --output-format streaming-json --always-approve` child +// per turn; tools run inside grok, so there's no host tool dispatch here. import { runBridge, @@ -16,11 +8,11 @@ import { type BridgeTurn, } from '@ai-sdk/harness/bridge'; import { spawn } from 'node:child_process'; -import { existsSync } from 'node:fs'; import { argv, env as procEnv, stdout } from 'node:process'; import { createInterface } from 'node:readline'; import type { StartMessage } from '../grok-build-bridge-protocol'; import { createStreamMapState, mapStreamLine } from '../grok-build-stream-map'; +import { prependGrokBuildBinToPath } from './grok-build-path'; const DEFAULT_GROK_MODEL = 'grok-build-0.1'; @@ -35,6 +27,11 @@ const workdir: string = args.workdir; const bridgeStateDir: string = args.bridgeStateDir; const bootstrapDir: string = args.bootstrapDir ?? workdir; +// Make the bootstrap-installed `grok` binary resolve ahead of any system copy +// by prepending its node_modules/.bin to PATH. Spawning the bare `grok` name +// (rather than an absolute path) then picks it up. Mirrors the OpenCode bridge. +prependGrokBuildBinToPath({ bootstrapDir, env: procEnv }); + // The latest grok CLI session id, learned from the terminal `end` event's // `sessionId`. Returned to the host on detach so a future process could resume // the grok thread via `-r/--resume`. @@ -50,7 +47,6 @@ await runBridge({ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise { const emit = (event: BridgeEvent) => turn.emit(event); - const grokBin = resolveGrokBinary(bootstrapDir); const cliArgs = [ '-p', start.prompt, @@ -67,7 +63,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise { // Resume the prior CLI thread in this workdir instead of starting fresh. if (start.continue) cliArgs.push('-c'); - const child = spawn(grokBin, cliArgs, { + const child = spawn('grok', cliArgs, { cwd: workdir, env: procEnv, stdio: ['ignore', 'pipe', 'pipe'], @@ -158,16 +154,6 @@ function captureSessionId(line: string): void { } } -/** - * Resolve the `grok` binary path. The bootstrap installs `@xai-official/grok` - * into the bootstrap dir's node_modules, exposing `./node_modules/.bin/grok`. - * Fall back to bare `grok` (PATH) when that shim is absent. - */ -function resolveGrokBinary(dir: string): string { - const local = `${dir}/node_modules/.bin/grok`; - return existsSync(local) ? local : 'grok'; -} - function parseArgs(rawArgs: string[]): { workdir?: string; bridgeStateDir?: string; diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts index 9b13e7045c85..7bac2bfe2001 100644 --- a/packages/harness-grok-build/src/grok-build-auth.ts +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -38,20 +38,8 @@ export function resolveGrokBuildEnv( return pickXai({}, processEnv); } -/** - * Map the generic resolved auth blob (from {@link resolveGrokBuildEnv}) onto the - * concrete environment variables the `grok` CLI actually reads. The CLI does - * NOT read `XAI_API_KEY` / `AI_GATEWAY_*` directly, so this translation is - * required before spawning. - * - * Decision: gateway-vs-direct is keyed off the presence of `AI_GATEWAY_API_KEY` - * in the resolved blob (the gateway branch of `resolveGrokBuildEnv` always sets - * it). The two shapes: - * - Direct xAI: `XAI_API_KEY=` (and pass model id `grok-build-0.1`). - * - Gateway: `GROK_MODELS_BASE_URL=` + - * `GROK_CODE_XAI_API_KEY=` (and pass model id - * `xai/grok-build-0.1`). - */ +// Translate the resolved auth blob into the env vars the grok CLI reads. +// Direct: XAI_API_KEY. Gateway (keyed off AI_GATEWAY_API_KEY): GROK_MODELS_BASE_URL + GROK_CODE_XAI_API_KEY. export function toGrokCliEnv( resolved: Record, ): Record { @@ -93,8 +81,7 @@ function pickGateway( env.AI_GATEWAY_API_KEY = apiKey; env.XAI_API_KEY = apiKey; } - // Always forward the gateway base URL (mirrors claude-code-auth); the gateway - // helper always returns a non-empty default. + // Always forward the gateway base URL (mirrors claude-code-auth). env.AI_GATEWAY_BASE_URL = baseUrl; env.XAI_BASE_URL = baseUrl; return env; diff --git a/packages/harness-grok-build/src/grok-build-bridge-protocol.ts b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts index 7357e87914d0..5a4c3151cc54 100644 --- a/packages/harness-grok-build/src/grok-build-bridge-protocol.ts +++ b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts @@ -6,22 +6,14 @@ import { } from '@ai-sdk/harness'; import { z } from 'zod/v4'; -/* - * Grok Build's bridge wire protocol. The outbound events, transport frames, - * shared inbound commands, and `bridge-ready` line all come from the shared - * `@ai-sdk/harness` protocol. The only Grok-specific piece is the `start` - * payload, which carries Grok CLI configuration. - */ - +// Bridge wire protocol. Everything but the grok-specific `start` payload comes from @ai-sdk/harness. export const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema; export type OutboundMessage = z.infer; export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({ model: z.string().optional(), - // Grok Build's plan-first execution loop. planMode: z.boolean().optional(), - // Resume signal. When true, the bridge resumes the prior CLI thread in the - // workdir instead of starting a fresh session. + // Resume the prior CLI thread instead of a fresh session. continue: z.boolean().optional(), }); export type StartMessage = z.infer; diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts index 54a1737fedb2..22e4923b05c8 100644 --- a/packages/harness-grok-build/src/grok-build-harness.test.ts +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -194,7 +194,7 @@ describe('grok-build doStart', () => { expect(spawnCalls).toHaveLength(1); expect(spawnCalls[0].command).toContain( - 'node /tmp/harness/grok-build/bridge.mjs --workdir /vercel/sandbox/grok-s1', + "node '/tmp/harness/grok-build/bridge.mjs' --workdir '/vercel/sandbox/grok-s1'", ); // Mapped direct-xai env var is forwarded to the bridge process. expect(spawnCalls[0].env.XAI_API_KEY).toBe('sk-direct'); diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 1f24e9b62cc9..146834b9aadf 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -37,14 +37,7 @@ import { type GrokBuildChannel = SandboxChannel; -/* - * Native tool name → common harness name mapping. - * - * TODO: Reconcile these native keys against captured Grok Build CLI fixture - * output once real CLI traces are available. The names below are borrowed from - * the Claude Code harness as a placeholder and may differ from what Grok Build - * actually emits. - */ +// Native tool name → common name. Placeholder names; reconcile with real CLI output. export const NATIVE_TO_COMMON: Readonly< Record > = { @@ -57,23 +50,13 @@ export const NATIVE_TO_COMMON: Readonly< WebSearch: 'webSearch', }; -/** - * Map a native Grok Build tool name to its cross-harness common name. - * Returns the native name unchanged if no mapping is found. - */ export function toCommonName( nativeName: string, ): HarnessV1BuiltinToolName | string { return NATIVE_TO_COMMON[nativeName] ?? nativeName; } -/* - * Every native tool the Grok Build CLI can invoke, declared as a ToolSet - * keyed by the common name where a mapping exists. - * - * TODO: Reconcile native tool names and input schemas against captured Grok - * Build CLI fixture output once real CLI traces are available. - */ +// Builtin tools, keyed by common name. Placeholder names/schemas; reconcile with real CLI output. export const GROK_BUILD_BUILTIN_TOOLS = { read: commonTool('read', { nativeName: 'Read', @@ -149,11 +132,7 @@ export const GROK_BUILD_BUILTIN_TOOLS = { const BOOTSTRAP_DIR = '/tmp/harness/grok-build'; -/* - * The grok model id the adapter pins when the consumer configures none. The - * direct (xAI) and gateway routes use different ids: direct uses the bare - * `grok-build-0.1`, while the gateway requires the `xai/` prefix. - */ +// Direct (xAI) uses the bare id; the gateway requires the `xai/` prefix. const DEFAULT_GROK_MODEL_DIRECT = 'grok-build-0.1'; const DEFAULT_GROK_MODEL_GATEWAY = 'xai/grok-build-0.1'; @@ -166,11 +145,7 @@ export type GrokBuildHarnessSettings = { readonly startupTimeoutMs?: number; }; -/** - * Adapter-specific lifecycle `data` payload. `sessionId` is the grok CLI - * session id (from the terminal `end` event) usable for `-r/--resume`; `bridge` - * carries live coordinates for a cross-process attach. - */ +// `sessionId` (from the `end` event) feeds `-r/--resume`; `bridge` carries attach coords. const lifecycleStateSchema = z.object({ sessionId: z.string().optional(), bridge: z @@ -204,9 +179,7 @@ async function readBridgeAsset(name: string): Promise { export function createGrokBuild( settings: GrokBuildHarnessSettings = {}, ): HarnessV1 { - // Per-instance cache: bridge assets are static, but keeping this in the - // factory closure (rather than module scope) avoids leaking state across - // separate createGrokBuild() instances. + // Per-instance cache (in the closure, not module scope, to avoid cross-instance leakage). let cachedBootstrap: HarnessV1Bootstrap | null = null; return { specificationVersion: 'harness-v1', @@ -275,8 +248,7 @@ export function createGrokBuild( const bridgeStateDir = `${sessionDataDir}/bridge`; const timeoutMs = settings.startupTimeoutMs ?? 120_000; - // Normalize each forwarded bridge diagnostics frame into the general - // `HarnessV1Diagnostic` and report it. + // Normalize forwarded bridge diagnostics frames and report them. const report = startOpts.observability?.report; const onDiagnostic = report ? (frame: Parameters[0]) => @@ -288,9 +260,7 @@ export function createGrokBuild( ) : undefined; - // Resolve auth, then translate the generic blob into the concrete env - // vars the grok CLI reads, and pick the matching model id (gateway needs - // the `xai/` prefix). + // Resolve auth → concrete grok CLI env vars, and pick the matching model id. const resolvedAuth = resolveGrokBuildEnv(settings.auth); const grokEnv = toGrokCliEnv(resolvedAuth); const isGateway = resolvedAuth.AI_GATEWAY_API_KEY != null; @@ -307,7 +277,7 @@ export function createGrokBuild( }; await session.run({ - command: `mkdir -p ${workDir} ${bridgeStateDir}`, + command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`, abortSignal: startOpts.abortSignal, }); @@ -319,7 +289,7 @@ export function createGrokBuild( }); const proc = await session.spawn({ - command: `node ${BOOTSTRAP_DIR}/bridge.mjs --workdir ${workDir} --bridge-state-dir ${bridgeStateDir} --bootstrap-dir ${BOOTSTRAP_DIR}`, + command: `node ${shellQuote(`${BOOTSTRAP_DIR}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(BOOTSTRAP_DIR)}`, env, abortSignal: startOpts.abortSignal, }); @@ -369,6 +339,11 @@ export function createGrokBuild( }; } +// Single-quote a value for safe use in a POSIX shell command +function shellQuote(value: string): string { + return `'${value.replace(/'/g, `'\\''`)}'`; +} + function resolveBridgePort( sandboxSession: HarnessV1NetworkSandboxSession, override: number | undefined, @@ -461,19 +436,10 @@ function createSession({ let stopped = false; let stopPromise: Promise | undefined; - /* - * Latest grok CLI session id, cached from the bridge's `bridge-detach` - * payload-bearing announcements is not available pre-detach; seed from - * lifecycle state so `doDetach`/`doStop` can include an id even before this - * process has finished a turn. - */ + // Latest grok CLI session id; seeded from lifecycle state so detach/stop have an id pre-turn. let latestGrokSessionId = resumeGrokSessionId; - /* - * Wire the channel into one turn's worth of events and return the control - * surface. Shared by `doPromptTurn` and `doContinueTurn` (which differ only - * in the `start` message they send afterwards). - */ + // Wire the channel into one turn and return the control surface (shared by prompt/continue). const wireTurn = (turnOpts: { emit: (event: HarnessV1StreamPart) => void; abortSignal?: AbortSignal; @@ -572,11 +538,7 @@ function createSession({ return { done }; }; - /* - * Tools execute inside grok via `--always-approve`, so the host never - * dispatches tools in this mode: `submitToolResult` / `submitToolApproval` - * are unsupported no-ops that match the `HarnessV1PromptControl` interface. - */ + // grok self-executes tools (`--always-approve`); these are unsupported no-ops. const unsupportedToolControl = { submitToolResult: async () => { throw new HarnessCapabilityUnsupportedError({ @@ -615,9 +577,7 @@ function createSession({ emit: continueOpts.emit, abortSignal: continueOpts.abortSignal, }); - // `doContinueTurn` carries no prompt; the grok CLI requires `-p`, so send - // a continuation nudge alongside `-c` (which resumes the prior thread in - // the workdir). Mirrors the codex adapter. + // No prompt on continue, but grok `-p` requires one — send a nudge with `-c`. channel.send({ type: 'start', prompt: 'Continue.', @@ -745,11 +705,7 @@ function createSession({ }; } -/* - * Reduce a `HarnessV1Prompt` to the plain user text the bridge passes to the - * grok CLI via `-p`. File and image parts are not yet supported — throw rather - * than silently drop them. - */ +// Reduce a prompt to plain text for grok `-p`. File/image parts are unsupported — throw. function extractUserText( prompt: Parameters[0]['prompt'], ): string { diff --git a/packages/harness-grok-build/src/grok-build-stream-map.ts b/packages/harness-grok-build/src/grok-build-stream-map.ts index 8208a31cf146..9a6613918141 100644 --- a/packages/harness-grok-build/src/grok-build-stream-map.ts +++ b/packages/harness-grok-build/src/grok-build-stream-map.ts @@ -1,23 +1,14 @@ import type { HarnessV1StreamPart } from '@ai-sdk/harness'; -// Extract V4 types from the finish part shape rather than importing from -// @ai-sdk/provider directly (not listed in package.json dependencies). +// V4 types via the finish part shape (@ai-sdk/provider isn't a dependency). type FinishPart = Extract; type LanguageModelV4FinishReason = FinishPart['finishReason']; type LanguageModelV4Usage = FinishPart['totalUsage']; -// --------------------------------------------------------------------------- -// State -// --------------------------------------------------------------------------- - export type StreamMapState = { - /** Whether we have already emitted a stream-start event. */ streamStarted: boolean; - /** Id of the currently open text block, or null. */ openTextId: string | null; - /** Id of the currently open reasoning block, or null. */ openReasoningId: string | null; - /** Counter used to mint unique block ids. */ nextId: number; }; @@ -30,26 +21,12 @@ export function createStreamMapState(): StreamMapState { }; } -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - function mintId(state: StreamMapState, prefix: string): string { return `${prefix}_${state.nextId++}`; } -/** - * Zero usage object. - * - * NOTE: token usage is unavailable in `grok --output-format streaming-json` - * mode — the CLI does not emit usage data in this surface. Reporting zeros - * here is intentional; real usage figures will be available once the ACP - * (Agent Communication Protocol) surface is supported (future follow-up). - */ +// streaming-json reports no token counts; `undefined` (not 0) signals "not reported". function unknownUsage(): LanguageModelV4Usage { - // streaming-json mode reports no token counts. Use `undefined` (not 0) so - // downstream consumers can distinguish "not reported" from "zero tokens used". - // Real usage will be available via the ACP surface (future follow-up). return { inputTokens: { total: undefined, @@ -82,30 +59,12 @@ function mapStopReason(raw: string | undefined): LanguageModelV4FinishReason { // Core mapping // --------------------------------------------------------------------------- -/** - * Map one raw (newline-delimited) JSON line from `grok -p ... --output-format - * streaming-json` to zero or more `HarnessV1StreamPart` events. - * - * Three event shapes exist in this mode: - * - `{"type":"thought","data":""}` — reasoning text delta - * - `{"type":"text","data":""}` — assistant text delta - * - `{"type":"end","stopReason":"EndTurn","sessionId":"...","requestId":"..."}` — terminal - * - * Tool-call/tool-result/file-change events and token usage are NOT emitted - * here — they are unavailable in this CLI surface and are a future follow-up - * via the ACP surface. - * - * Pure function with mutable state passed in — no I/O, never throws. - */ +// Map one streaming-json line (`thought`/`text`/`end`) to stream parts. Pure, never throws. export function mapStreamLine( rawLine: string, state: StreamMapState, ): HarnessV1StreamPart[] { - // Safe parse — return [] on any error, never throw. - // NOTE: `safeParseJSON` from `@ai-sdk/provider-utils` is async and cannot be - // used in a synchronous line processor. We use a local try/catch here which - // is semantically equivalent to the sync core of `safeParseJSON` (no schema - // validation needed — we validate shapes via runtime property access below). + // JSON.parse, not async safeParseJSON, since this runs per line synchronously. let msg: unknown; try { msg = JSON.parse(rawLine); @@ -119,7 +78,6 @@ export function mapStreamLine( const parts: HarnessV1StreamPart[] = []; - // Emit stream-start exactly once, before any other event. function ensureStreamStart() { if (!state.streamStarted) { state.streamStarted = true; @@ -127,7 +85,6 @@ export function mapStreamLine( } } - // Close an open text block if any. function closeTextBlock() { if (state.openTextId !== null) { parts.push({ type: 'text-end', id: state.openTextId }); @@ -135,7 +92,6 @@ export function mapStreamLine( } } - // Close an open reasoning block if any. function closeReasoningBlock() { if (state.openReasoningId !== null) { parts.push({ type: 'reasoning-end', id: state.openReasoningId }); @@ -149,10 +105,7 @@ export function mapStreamLine( case 'thought': { const data = typeof anyMsg['data'] === 'string' ? anyMsg['data'] : ''; - // If a text block is somehow open, close it first (shouldn't normally happen). closeTextBlock(); - - // Open reasoning block if not already open. if (state.openReasoningId === null) { const id = mintId(state, 'reasoning'); state.openReasoningId = id; @@ -170,10 +123,7 @@ export function mapStreamLine( case 'text': { const data = typeof anyMsg['data'] === 'string' ? anyMsg['data'] : ''; - // Close any open reasoning block before switching to text. closeReasoningBlock(); - - // Open text block if not already open. if (state.openTextId === null) { const id = mintId(state, 'text'); state.openTextId = id; @@ -198,15 +148,12 @@ export function mapStreamLine( parts.push({ type: 'finish', finishReason: mapStopReason(stopReason), - // NOTE: usage is unavailable in streaming-json mode; zeros are intentional. - // Real token counts will be available via the ACP surface (future follow-up). totalUsage: unknownUsage(), }); break; } default: { - // Unknown event type → raw passthrough. parts.push({ type: 'raw', rawValue: msg }); break; } From bec59539d237a532035f62acc6b969c9c9df7ef0 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 13:45:53 -0700 Subject: [PATCH 15/23] wire into e2e-tui and e2e-next apps --- .../harness/grok-build/ai-sdk-coding-agent.ts | 52 +++++++ .../agent/harness/grok-build/basic-agent.ts | 29 ++++ .../harness/grok-build/ai-sdk-coding/route.ts | 41 +++++ .../grok-build/basic-with-stop/route.ts | 37 +++++ .../app/api/harness/grok-build/basic/route.ts | 35 +++++ .../harness/grok-build/ai-sdk-coding/page.tsx | 19 +++ .../grok-build/basic-with-stop/page.tsx | 19 +++ .../app/harness/grok-build/basic/page.tsx | 19 +++ examples/harness-e2e-next/app/page.tsx | 5 + .../components/grok-build-harness-chat.tsx | 142 ++++++++++++++++++ examples/harness-e2e-next/package.json | 1 + .../agents/grok-build/ai-sdk-coding-agent.ts | 52 +++++++ .../agents/grok-build/basic-agent.ts | 29 ++++ .../harness/grok-build/ai-sdk-coding.ts | 8 + .../harness/grok-build/basic.ts | 8 + examples/harness-e2e-tui/package.json | 1 + pnpm-lock.yaml | 6 + 17 files changed, 503 insertions(+) create mode 100644 examples/harness-e2e-next/agent/harness/grok-build/ai-sdk-coding-agent.ts create mode 100644 examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts create mode 100644 examples/harness-e2e-next/app/api/harness/grok-build/ai-sdk-coding/route.ts create mode 100644 examples/harness-e2e-next/app/api/harness/grok-build/basic-with-stop/route.ts create mode 100644 examples/harness-e2e-next/app/api/harness/grok-build/basic/route.ts create mode 100644 examples/harness-e2e-next/app/harness/grok-build/ai-sdk-coding/page.tsx create mode 100644 examples/harness-e2e-next/app/harness/grok-build/basic-with-stop/page.tsx create mode 100644 examples/harness-e2e-next/app/harness/grok-build/basic/page.tsx create mode 100644 examples/harness-e2e-next/components/grok-build-harness-chat.tsx create mode 100644 examples/harness-e2e-tui/agents/grok-build/ai-sdk-coding-agent.ts create mode 100644 examples/harness-e2e-tui/agents/grok-build/basic-agent.ts create mode 100644 examples/harness-e2e-tui/harness/grok-build/ai-sdk-coding.ts create mode 100644 examples/harness-e2e-tui/harness/grok-build/basic.ts diff --git a/examples/harness-e2e-next/agent/harness/grok-build/ai-sdk-coding-agent.ts b/examples/harness-e2e-next/agent/harness/grok-build/ai-sdk-coding-agent.ts new file mode 100644 index 000000000000..17088d4ab740 --- /dev/null +++ b/examples/harness-e2e-next/agent/harness/grok-build/ai-sdk-coding-agent.ts @@ -0,0 +1,52 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import type { InferUITools, UIMessage } from 'ai'; + +// Default sandbox resources won't allow for a full parallel build of all packages. +// Not worth bumping all demo sandboxes' resources for just this, we can easily +// work around this by guiding the harness. +const instructions = ` +Building all packages at once (e.g. running \`pnpm build\` or \`pnpm build:packages\`) +will exceed sandbox memory. When asked to do this, use the corresponding +\`pnpm exec turbo\` call directly with a lower \`--concurrency=4\` flag. +`; + +export const aiSdkCodingGrokBuildHarnessAgent = new HarnessAgent({ + harness: grokBuild, + instructions, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), + onSandboxSession: async ({ session, sessionWorkDir, abortSignal }) => { + const result = await session.run({ + command: + 'test -d .git || git clone --depth 1 https://github.com/vercel/ai.git .', + workingDirectory: sessionWorkDir, + abortSignal, + }); + if (result.exitCode !== 0) { + throw new Error( + `Failed to clone vercel/ai (exit ${result.exitCode}): ${result.stderr}`, + ); + } + + const installResult = await session.run({ + command: 'test -d node_modules || pnpm install', + workingDirectory: sessionWorkDir, + abortSignal, + }); + if (installResult.exitCode !== 0) { + throw new Error( + `Failed to install dependencies (exit ${installResult.exitCode}): ${installResult.stderr}`, + ); + } + }, +}); + +export type AiSdkCodingGrokBuildHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; diff --git a/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts b/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts new file mode 100644 index 000000000000..f4f088ff6128 --- /dev/null +++ b/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts @@ -0,0 +1,29 @@ +import { + HarnessAgent, + createFileReporter, + createTraceTreeReporter, +} from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import type { InferUITools, UIMessage } from 'ai'; + +export const grokBuildHarnessAgent = new HarnessAgent({ + harness: grokBuild, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), + debug: { enabled: true }, + telemetry: { + integrations: [ + createTraceTreeReporter(), + createFileReporter({ dir: '.harness-observability/grok-build/basic' }), + ], + }, +}); + +export type GrokBuildHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; diff --git a/examples/harness-e2e-next/app/api/harness/grok-build/ai-sdk-coding/route.ts b/examples/harness-e2e-next/app/api/harness/grok-build/ai-sdk-coding/route.ts new file mode 100644 index 000000000000..d3b43cd12816 --- /dev/null +++ b/examples/harness-e2e-next/app/api/harness/grok-build/ai-sdk-coding/route.ts @@ -0,0 +1,41 @@ +import { aiSdkCodingGrokBuildHarnessAgent } from '@/agent/harness/grok-build/ai-sdk-coding-agent'; +import { + detachAndPersist, + resumeOrCreateSession, +} from '@/util/harness-resume-store'; +import { + convertToModelMessages, + createUIMessageStreamResponse, + toUIMessageStream, + type UIMessage, +} from 'ai'; + +export async function POST(request: Request) { + const body: { + id?: string; + messages: UIMessage[]; + } = await request.json(); + + if (!body.id) { + return new Response('Missing chat id', { status: 400 }); + } + const chatId = body.id; + const messages = await convertToModelMessages(body.messages); + + const session = await resumeOrCreateSession( + aiSdkCodingGrokBuildHarnessAgent, + chatId, + ); + + const result = await aiSdkCodingGrokBuildHarnessAgent.stream({ + session, + messages, + }); + + return createUIMessageStreamResponse({ + stream: toUIMessageStream({ + stream: result.stream, + onFinish: () => detachAndPersist(chatId, session), + }), + }); +} diff --git a/examples/harness-e2e-next/app/api/harness/grok-build/basic-with-stop/route.ts b/examples/harness-e2e-next/app/api/harness/grok-build/basic-with-stop/route.ts new file mode 100644 index 000000000000..09ab4d475fbb --- /dev/null +++ b/examples/harness-e2e-next/app/api/harness/grok-build/basic-with-stop/route.ts @@ -0,0 +1,37 @@ +import { grokBuildHarnessAgent } from '@/agent/harness/grok-build/basic-agent'; +import { + resumeOrCreateSession, + stopAndPersist, +} from '@/util/harness-resume-store'; +import { + convertToModelMessages, + createUIMessageStreamResponse, + toUIMessageStream, + type UIMessage, +} from 'ai'; + +export async function POST(request: Request) { + const body: { + id?: string; + messages: UIMessage[]; + } = await request.json(); + + if (!body.id) { + return new Response('Missing chat id', { status: 400 }); + } + const chatId = body.id; + const messages = await convertToModelMessages(body.messages); + + const session = await resumeOrCreateSession(grokBuildHarnessAgent, chatId); + + const result = await grokBuildHarnessAgent.stream({ session, messages }); + + return createUIMessageStreamResponse({ + stream: toUIMessageStream({ + stream: result.stream, + // Stop the session at the end of the turn so the next request resumes + // from the persisted snapshot rather than attaching to a parked bridge. + onFinish: () => stopAndPersist(chatId, session), + }), + }); +} diff --git a/examples/harness-e2e-next/app/api/harness/grok-build/basic/route.ts b/examples/harness-e2e-next/app/api/harness/grok-build/basic/route.ts new file mode 100644 index 000000000000..41f299c49278 --- /dev/null +++ b/examples/harness-e2e-next/app/api/harness/grok-build/basic/route.ts @@ -0,0 +1,35 @@ +import { grokBuildHarnessAgent } from '@/agent/harness/grok-build/basic-agent'; +import { + detachAndPersist, + resumeOrCreateSession, +} from '@/util/harness-resume-store'; +import { + convertToModelMessages, + createUIMessageStreamResponse, + toUIMessageStream, + type UIMessage, +} from 'ai'; + +export async function POST(request: Request) { + const body: { + id?: string; + messages: UIMessage[]; + } = await request.json(); + + if (!body.id) { + return new Response('Missing chat id', { status: 400 }); + } + const chatId = body.id; + const messages = await convertToModelMessages(body.messages); + + const session = await resumeOrCreateSession(grokBuildHarnessAgent, chatId); + + const result = await grokBuildHarnessAgent.stream({ session, messages }); + + return createUIMessageStreamResponse({ + stream: toUIMessageStream({ + stream: result.stream, + onFinish: () => detachAndPersist(chatId, session), + }), + }); +} diff --git a/examples/harness-e2e-next/app/harness/grok-build/ai-sdk-coding/page.tsx b/examples/harness-e2e-next/app/harness/grok-build/ai-sdk-coding/page.tsx new file mode 100644 index 000000000000..0a110f1f759c --- /dev/null +++ b/examples/harness-e2e-next/app/harness/grok-build/ai-sdk-coding/page.tsx @@ -0,0 +1,19 @@ +import ChatIdProvider from '@/components/chat-id-provider'; +import GrokBuildHarnessChat from '@/components/grok-build-harness-chat'; + +export const metadata = { + title: 'Grok Build — AI SDK Checkout', +}; + +const STORAGE_KEY = 'harness-grok-build-ai-sdk-coding-chat-id'; + +export default function HarnessGrokBuildAiSdkCodingPage() { + return ( + + + + ); +} diff --git a/examples/harness-e2e-next/app/harness/grok-build/basic-with-stop/page.tsx b/examples/harness-e2e-next/app/harness/grok-build/basic-with-stop/page.tsx new file mode 100644 index 000000000000..70cf9f511e3e --- /dev/null +++ b/examples/harness-e2e-next/app/harness/grok-build/basic-with-stop/page.tsx @@ -0,0 +1,19 @@ +import ChatIdProvider from '@/components/chat-id-provider'; +import GrokBuildHarnessChat from '@/components/grok-build-harness-chat'; + +export const metadata = { + title: 'Grok Build — Basic (with stop)', +}; + +const STORAGE_KEY = 'harness-grok-build-basic-with-stop-chat-id'; + +export default function HarnessGrokBuildBasicWithStopPage() { + return ( + + + + ); +} diff --git a/examples/harness-e2e-next/app/harness/grok-build/basic/page.tsx b/examples/harness-e2e-next/app/harness/grok-build/basic/page.tsx new file mode 100644 index 000000000000..0e818523cb9d --- /dev/null +++ b/examples/harness-e2e-next/app/harness/grok-build/basic/page.tsx @@ -0,0 +1,19 @@ +import ChatIdProvider from '@/components/chat-id-provider'; +import GrokBuildHarnessChat from '@/components/grok-build-harness-chat'; + +export const metadata = { + title: 'Grok Build — Basic', +}; + +const STORAGE_KEY = 'harness-grok-build-basic-chat-id'; + +export default function HarnessGrokBuildPage() { + return ( + + + + ); +} diff --git a/examples/harness-e2e-next/app/page.tsx b/examples/harness-e2e-next/app/page.tsx index fd7324c9058f..80c33b1aa230 100644 --- a/examples/harness-e2e-next/app/page.tsx +++ b/examples/harness-e2e-next/app/page.tsx @@ -42,6 +42,11 @@ const HARNESSES = [ 'weather-approval', ], }, + { + slug: 'grok-build', + label: 'Grok Build', + variants: ['basic', 'basic-with-stop', 'ai-sdk-coding'], + }, ] as const; const VARIANT_LABELS: Record = Object.fromEntries( diff --git a/examples/harness-e2e-next/components/grok-build-harness-chat.tsx b/examples/harness-e2e-next/components/grok-build-harness-chat.tsx new file mode 100644 index 000000000000..d61efb77a059 --- /dev/null +++ b/examples/harness-e2e-next/components/grok-build-harness-chat.tsx @@ -0,0 +1,142 @@ +'use client'; + +import type { GrokBuildHarnessAgentMessage } from '@/agent/harness/grok-build/basic-agent'; +import { Response } from '@/components/ai-elements/response'; +import { useChatId } from '@/components/chat-id-provider'; +import ChatInput from '@/components/chat-input'; +import DynamicToolView from '@/components/tool/dynamic-tool-view'; +import HarnessBashToolView from '@/components/tool/harness-bash-tool-view'; +import HarnessToolView from '@/components/tool/harness-tool-view'; +import { useChat } from '@ai-sdk/react'; +import { DefaultChatTransport } from 'ai'; + +export default function GrokBuildHarnessChat({ + apiRoute, + exampleLabel, +}: { + apiRoute: string; + exampleLabel: string; +}) { + const { chatId, resetChatId } = useChatId(); + const { error, status, sendMessage, messages, regenerate } = + useChat({ + id: chatId, + transport: new DefaultChatTransport({ + api: apiRoute, + }), + }); + + return ( +
+

Grok Build — {exampleLabel}

+

+ chat id: {chatId} + +

+ + {messages.map(message => ( +
+ {message.role === 'user' ? 'You: ' : 'AI: '} + {message.parts.map((part, index) => { + switch (part.type) { + case 'text': { + return ( + + {part.text} + + ); + } + case 'reasoning': { + return ( + + {part.text} + + ); + } + case 'file': + case 'reasoning-file': { + if (part.mediaType.startsWith('image/')) { + return ( + // eslint-disable-next-line @next/next/no-img-element + Generated image + ); + } + return null; + } + case 'tool-bash': { + return ; + } + case 'dynamic-tool': { + if (part.toolName === 'fileChange') { + if (typeof part.input !== 'object' || part.input === null) { + return null; + } + return ( + + ); + } + return ; + } + } + })} +
+ ))} + + {status === 'submitted' && ( +
+ )} + + {error && ( +
+
+ {error.message || String(error)} +
+ +
+ )} + +
+ + sendMessage({ text })} + /> +
+ ); +} diff --git a/examples/harness-e2e-next/package.json b/examples/harness-e2e-next/package.json index d18ffc262242..15148291ff19 100644 --- a/examples/harness-e2e-next/package.json +++ b/examples/harness-e2e-next/package.json @@ -12,6 +12,7 @@ "@ai-sdk/harness": "workspace:*", "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", + "@ai-sdk/harness-grok-build": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/provider-utils": "workspace:*", "@ai-sdk/react": "workspace:*", diff --git a/examples/harness-e2e-tui/agents/grok-build/ai-sdk-coding-agent.ts b/examples/harness-e2e-tui/agents/grok-build/ai-sdk-coding-agent.ts new file mode 100644 index 000000000000..17088d4ab740 --- /dev/null +++ b/examples/harness-e2e-tui/agents/grok-build/ai-sdk-coding-agent.ts @@ -0,0 +1,52 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import type { InferUITools, UIMessage } from 'ai'; + +// Default sandbox resources won't allow for a full parallel build of all packages. +// Not worth bumping all demo sandboxes' resources for just this, we can easily +// work around this by guiding the harness. +const instructions = ` +Building all packages at once (e.g. running \`pnpm build\` or \`pnpm build:packages\`) +will exceed sandbox memory. When asked to do this, use the corresponding +\`pnpm exec turbo\` call directly with a lower \`--concurrency=4\` flag. +`; + +export const aiSdkCodingGrokBuildHarnessAgent = new HarnessAgent({ + harness: grokBuild, + instructions, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), + onSandboxSession: async ({ session, sessionWorkDir, abortSignal }) => { + const result = await session.run({ + command: + 'test -d .git || git clone --depth 1 https://github.com/vercel/ai.git .', + workingDirectory: sessionWorkDir, + abortSignal, + }); + if (result.exitCode !== 0) { + throw new Error( + `Failed to clone vercel/ai (exit ${result.exitCode}): ${result.stderr}`, + ); + } + + const installResult = await session.run({ + command: 'test -d node_modules || pnpm install', + workingDirectory: sessionWorkDir, + abortSignal, + }); + if (installResult.exitCode !== 0) { + throw new Error( + `Failed to install dependencies (exit ${installResult.exitCode}): ${installResult.stderr}`, + ); + } + }, +}); + +export type AiSdkCodingGrokBuildHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; diff --git a/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts b/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts new file mode 100644 index 000000000000..f4f088ff6128 --- /dev/null +++ b/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts @@ -0,0 +1,29 @@ +import { + HarnessAgent, + createFileReporter, + createTraceTreeReporter, +} from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import type { InferUITools, UIMessage } from 'ai'; + +export const grokBuildHarnessAgent = new HarnessAgent({ + harness: grokBuild, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), + debug: { enabled: true }, + telemetry: { + integrations: [ + createTraceTreeReporter(), + createFileReporter({ dir: '.harness-observability/grok-build/basic' }), + ], + }, +}); + +export type GrokBuildHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; diff --git a/examples/harness-e2e-tui/harness/grok-build/ai-sdk-coding.ts b/examples/harness-e2e-tui/harness/grok-build/ai-sdk-coding.ts new file mode 100644 index 000000000000..9603d5c35ba8 --- /dev/null +++ b/examples/harness-e2e-tui/harness/grok-build/ai-sdk-coding.ts @@ -0,0 +1,8 @@ +import { aiSdkCodingGrokBuildHarnessAgent } from '../../agents/grok-build/ai-sdk-coding-agent'; +import { runTUI } from '../../lib/run-tui'; + +await runTUI({ + agent: aiSdkCodingGrokBuildHarnessAgent, + entrypointUrl: import.meta.url, + title: 'Grok Build — AI SDK Coding', +}); diff --git a/examples/harness-e2e-tui/harness/grok-build/basic.ts b/examples/harness-e2e-tui/harness/grok-build/basic.ts new file mode 100644 index 000000000000..e301101f74d1 --- /dev/null +++ b/examples/harness-e2e-tui/harness/grok-build/basic.ts @@ -0,0 +1,8 @@ +import { grokBuildHarnessAgent } from '../../agents/grok-build/basic-agent'; +import { runTUI } from '../../lib/run-tui'; + +await runTUI({ + agent: grokBuildHarnessAgent, + entrypointUrl: import.meta.url, + title: 'Grok Build — Basic', +}); diff --git a/examples/harness-e2e-tui/package.json b/examples/harness-e2e-tui/package.json index a2b6343fc300..b7588632b184 100644 --- a/examples/harness-e2e-tui/package.json +++ b/examples/harness-e2e-tui/package.json @@ -10,6 +10,7 @@ "@ai-sdk/harness": "workspace:*", "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", + "@ai-sdk/harness-grok-build": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/sandbox-vercel": "workspace:*", "@ai-sdk/tui": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72cc836adcf1..77ed8384f150 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -602,6 +602,9 @@ importers: '@ai-sdk/harness-codex': specifier: workspace:* version: link:../../packages/harness-codex + '@ai-sdk/harness-grok-build': + specifier: workspace:* + version: link:../../packages/harness-grok-build '@ai-sdk/harness-pi': specifier: workspace:* version: link:../../packages/harness-pi @@ -681,6 +684,9 @@ importers: '@ai-sdk/harness-codex': specifier: workspace:* version: link:../../packages/harness-codex + '@ai-sdk/harness-grok-build': + specifier: workspace:* + version: link:../../packages/harness-grok-build '@ai-sdk/harness-pi': specifier: workspace:* version: link:../../packages/harness-pi From d7473b077e6a0144c6b9d08e8c89d99562b3bb25 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 14:38:40 -0700 Subject: [PATCH 16/23] feat(harness-grok-build): conditionally forward bridge stderr based on debug config --- packages/harness-grok-build/src/grok-build-harness.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 146834b9aadf..275d540f8658 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -307,7 +307,7 @@ export function createGrokBuild( new Error('grok-build bridge exited before becoming ready.'), }); void drainRest(proc.stdout); - void forwardBridgeStderr(proc.stderr); + void forwardBridgeStderr(proc.stderr, startOpts.observability?.debug); const wsUrl = (await sandboxSession.getPortUrl({ @@ -374,9 +374,15 @@ function openWebSocket(url: string): Promise { }); } +// Live-forward bridge stderr to the terminal only under debug. grok logs +// recoverable errors (e.g. a spurious `grok-build` model probe that 404s) to +// stderr mid-turn; printing those by default paints over TUI consumers. Fatal +// exits still surface their stderr tail via the bridge's close handler. async function forwardBridgeStderr( stream: ReadableStream, + debug: HarnessV1DebugConfig | undefined, ): Promise { + if (debug?.enabled !== true) return; try { const reader = stream.pipeThrough(new TextDecoderStream()).getReader(); while (true) { From 45d2b83571f3b9268f59b71595e53483a4029d6a Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 14:49:20 -0700 Subject: [PATCH 17/23] fix(harness-grok-build): continue thread on resume and apply turn instructions --- .../src/grok-build-harness.test.ts | 56 +++++++++++++++++++ .../src/grok-build-harness.ts | 16 +++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts index 22e4923b05c8..d1675be3f17a 100644 --- a/packages/harness-grok-build/src/grok-build-harness.test.ts +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -291,6 +291,62 @@ describe('grok-build doStart', () => { expect(session.isResume).toBe(true); }); + it('continues the grok thread on the first prompt after resume, then stops', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + resumeFrom: { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { sessionId: 'grok-sess-123' }, + }, + }); + + await session.doPromptTurn({ prompt: 'first', emit: () => {} }); + await session.doPromptTurn({ prompt: 'second', emit: () => {} }); + const starts = sentMessages.filter(m => m.type === 'start'); + expect(starts[0]).toMatchObject({ prompt: 'first', continue: true }); + expect(starts[1]?.continue).toBeUndefined(); + }); + + it('applies instructions once, on the first fresh-session prompt', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + + await session.doPromptTurn({ + prompt: 'hello', + instructions: 'BE TERSE', + emit: () => {}, + }); + await session.doPromptTurn({ + prompt: 'again', + instructions: 'BE TERSE', + emit: () => {}, + }); + const starts = sentMessages.filter(m => m.type === 'start'); + expect(starts[0]?.prompt).toBe('BE TERSE\n\nhello'); + expect(starts[1]?.prompt).toBe('again'); + }); + it('rejects manual compaction', async () => { const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); const spawnCalls: Array<{ diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 275d540f8658..089b766bc0cd 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -445,6 +445,11 @@ function createSession({ // Latest grok CLI session id; seeded from lifecycle state so detach/stop have an id pre-turn. let latestGrokSessionId = resumeGrokSessionId; + // On a resumed session, the first prompt must continue the prior grok thread. + let continueOnNextPrompt = isResume; + // Session-level instructions are applied once, on the first fresh-session prompt. + let instructionsApplied = false; + // Wire the channel into one turn and return the control surface (shared by prompt/continue). const wireTurn = (turnOpts: { emit: (event: HarnessV1StreamPart) => void; @@ -571,10 +576,19 @@ function createSession({ emit: promptOpts.emit, abortSignal: promptOpts.abortSignal, }); + let prompt = extractUserText(promptOpts.prompt); + // Apply session instructions once, on the first prompt of a fresh session. + if (promptOpts.instructions && !instructionsApplied && !isResume) { + prompt = `${promptOpts.instructions}\n\n${prompt}`; + } + instructionsApplied = true; + const shouldContinue = continueOnNextPrompt; + continueOnNextPrompt = false; channel.send({ type: 'start', - prompt: extractUserText(promptOpts.prompt), + prompt, ...(model ? { model } : {}), + ...(shouldContinue ? { continue: true } : {}), }); return { ...unsupportedToolControl, done }; }, From f23f83b36ce5d6298b884a822d5d9bed3897c658 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 14:58:10 -0700 Subject: [PATCH 18/23] adding changelog --- packages/harness-grok-build/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/harness-grok-build/CHANGELOG.md diff --git a/packages/harness-grok-build/CHANGELOG.md b/packages/harness-grok-build/CHANGELOG.md new file mode 100644 index 000000000000..3ba038dbf213 --- /dev/null +++ b/packages/harness-grok-build/CHANGELOG.md @@ -0,0 +1 @@ +# @ai-sdk/harness-grok-build From 65439bb5989ec9812df64641e3422126363ce8c2 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 15:18:05 -0700 Subject: [PATCH 19/23] docs(harness-grok-build): add README, provider docs, and examples --- .../05-harness-adapters.mdx | 2 + content/docs/03-ai-sdk-harnesses/index.mdx | 4 +- .../02-ai-sdk-harnesses/04-grok-build.mdx | 170 ++++++++++++++++++ .../providers/02-ai-sdk-harnesses/index.mdx | 5 + .../harness-agent/grok-build/multi-turn.ts | 41 +++++ .../src/harness-agent/grok-build/resume.ts | 61 +++++++ .../harness-agent/grok-build/stream-text.ts | 37 ++++ .../grok-build/with-provided-sandbox.ts | 40 +++++ packages/harness-grok-build/README.md | 62 +++++++ 9 files changed, 420 insertions(+), 2 deletions(-) create mode 100644 content/providers/02-ai-sdk-harnesses/04-grok-build.mdx create mode 100644 examples/ai-functions/src/harness-agent/grok-build/multi-turn.ts create mode 100644 examples/ai-functions/src/harness-agent/grok-build/resume.ts create mode 100644 examples/ai-functions/src/harness-agent/grok-build/stream-text.ts create mode 100644 examples/ai-functions/src/harness-agent/grok-build/with-provided-sandbox.ts create mode 100644 packages/harness-grok-build/README.md diff --git a/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx b/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx index de55a1d85e62..9ec42d49e41a 100644 --- a/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +++ b/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx @@ -17,6 +17,7 @@ The AI SDK includes the following harness adapters: - [Claude Code](/providers/ai-sdk-harnesses/claude-code) (`@ai-sdk/harness-claude-code`) - [Codex](/providers/ai-sdk-harnesses/codex) (`@ai-sdk/harness-codex`) - [Pi](/providers/ai-sdk-harnesses/pi) (`@ai-sdk/harness-pi`) +- [Grok Build](/providers/ai-sdk-harnesses/grok-build) (`@ai-sdk/harness-grok-build`) ### Coming Soon @@ -33,3 +34,4 @@ The AI SDK includes the following harness adapters: | [Claude Code](/providers/ai-sdk-harnesses/claude-code) | Sandbox bridge | | | | | [Codex](/providers/ai-sdk-harnesses/codex) | Sandbox bridge | | | | | [Pi](/providers/ai-sdk-harnesses/pi) | Host process | | | | +| [Grok Build](/providers/ai-sdk-harnesses/grok-build) | Sandbox bridge | | | | diff --git a/content/docs/03-ai-sdk-harnesses/index.mdx b/content/docs/03-ai-sdk-harnesses/index.mdx index ebc9392b167d..59f816365f3b 100644 --- a/content/docs/03-ai-sdk-harnesses/index.mdx +++ b/content/docs/03-ai-sdk-harnesses/index.mdx @@ -6,7 +6,7 @@ description: Use established agent harnesses through the AI SDK. # AI SDK Harnesses The harness section covers the AI SDK harness abstraction: a uniform API for -running established agent harnesses such as Claude Code, Codex, and Pi. +running established agent harnesses such as Claude Code, Codex, Pi, and Grok Build. + Harness packages are **experimental**. Expect breaking changes between + releases as this early API gets further refined. + + +## Setup + + + + + + + + + + + + + + + + +The adapter installs the `grok` CLI inside the sandbox when the first session +starts. This requires network egress for the bootstrap install. + +## Import + +```ts +import { grokBuild, createGrokBuild } from '@ai-sdk/harness-grok-build'; +``` + +`grokBuild` is equivalent to `createGrokBuild()` with its default configuration. + +## Basic Usage + +```ts +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; + +const agent = new HarnessAgent({ + harness: grokBuild, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), +}); + +const session = await agent.createSession(); + +let exitCode = 0; +try { + const result = await agent.stream({ + session, + prompt: 'In one sentence, what is the capital of France?', + }); + + for await (const part of result.stream) { + if (part.type === 'text-delta') { + process.stdout.write(part.text); + } + } +} catch (err) { + exitCode = 1; + console.error(err); +} finally { + await session.destroy(); + process.exit(exitCode); +} +``` + +To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for +Vercel Sandbox, and one of the variables listed under [authentication](#authentication) +for Grok. + +## Adapter Settings + +Use `createGrokBuild()` to configure the runtime: + +```ts +const harness = createGrokBuild({ + model: 'grok-code-fast-1', + planMode: true, +}); +``` + +Settings: + +- `auth`: xAI or AI Gateway authentication settings. +- `model`: Grok model id. If omitted, the adapter uses its pinned default. +- `planMode`: run the CLI in plan mode. +- `port`: bridge port override. +- `startupTimeoutMs`: maximum time to wait for the bridge to start. + +## Authentication + +By default, authentication is resolved from the host environment and forwarded +to the sandbox bridge. The adapter checks for AI Gateway and xAI credentials. + +Supported environment variables: + +- `XAI_API_KEY` (direct) +- `AI_GATEWAY_API_KEY` (AI Gateway) +- `VERCEL_OIDC_TOKEN` (AI Gateway) + +The CLI maps these internally to `GROK_MODELS_BASE_URL` / `GROK_CODE_XAI_API_KEY`. + +You can also pass explicit auth settings: + +```ts +const harness = createGrokBuild({ + auth: { + xai: { + apiKey: process.env.XAI_API_KEY, + }, + }, +}); +``` + +## Sandbox + +Grok Build requires a network sandbox with at least one exposed TCP port, +e.g. `@ai-sdk/sandbox-vercel`: + +```ts +const sandbox = createVercelSandbox({ + runtime: 'node24', + ports: [4000], +}); +``` + +## Known limitations + +The grok CLI's `--output-format streaming-json` surface is narrow: + +- Streams reasoning and text only — no tool-call, tool-result, or file-change + events, and no token usage. +- Allow-all permission mode only. The CLI runs with `--always-approve` and + executes tools itself; use `permissionMode: 'allow-all'`. +- No compaction. + +## Related + +- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) +- [Harness tools](/docs/ai-sdk-harnesses/tools) +- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) diff --git a/content/providers/02-ai-sdk-harnesses/index.mdx b/content/providers/02-ai-sdk-harnesses/index.mdx index bb85e24925d8..0b372d3fc855 100644 --- a/content/providers/02-ai-sdk-harnesses/index.mdx +++ b/content/providers/02-ai-sdk-harnesses/index.mdx @@ -26,6 +26,11 @@ and response primitives. description: 'Use Pi through the AI SDK harness abstraction.', href: '/providers/ai-sdk-harnesses/pi', }, + { + title: 'Grok Build', + description: 'Use Grok Build through the AI SDK harness abstraction.', + href: '/providers/ai-sdk-harnesses/grok-build', + }, ]} /> diff --git a/examples/ai-functions/src/harness-agent/grok-build/multi-turn.ts b/examples/ai-functions/src/harness-agent/grok-build/multi-turn.ts new file mode 100644 index 000000000000..15b5012ab6c4 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/grok-build/multi-turn.ts @@ -0,0 +1,41 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; + +run(async () => { + const sandbox = createVercelSandbox({ + runtime: 'node24', + ports: [4000], + timeout: 10 * 60 * 1000, + }); + const agent = new HarnessAgent({ + harness: grokBuild, + sandbox, + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + console.log('--- turn 1 ---'); + const first = await agent.stream({ + session, + prompt: 'My name is Felix. Remember it.', + }); + await printFullStream({ result: first }); + + console.log('--- turn 2 ---'); + const second = await agent.stream({ + session, + prompt: 'What is my name? Answer in one word.', + }); + await printFullStream({ result: second }); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + process.exit(exitCode); + } +}); diff --git a/examples/ai-functions/src/harness-agent/grok-build/resume.ts b/examples/ai-functions/src/harness-agent/grok-build/resume.ts new file mode 100644 index 000000000000..11bef0d77a28 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/grok-build/resume.ts @@ -0,0 +1,61 @@ +/* + * Cross-process resume smoke test for the Grok Build harness. + * + * Within a single Node process this simulates the REST-server flow: turn 1 + * runs, the session is stopped, the agent reference is dropped, and a fresh + * `HarnessAgent` instance picks the conversation back up using the persisted + * `HarnessAgentResumeSessionState`. The resume payload carries the grok CLI + * session id, which feeds `--resume` on the second turn so the model + * remembers the name from turn 1. + */ +import { + HarnessAgent, + type HarnessAgentResumeSessionState, +} from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; + +run(async () => { + const sandbox = createVercelSandbox({ + runtime: 'node24', + ports: [4000], + timeout: 10 * 60 * 1000, + }); + + // Turn 1: introduce the name. + let sessionId: string; + let resumeState: HarnessAgentResumeSessionState; + { + const agent = new HarnessAgent({ harness: grokBuild, sandbox }); + const session = await agent.createSession(); + sessionId = session.sessionId; + console.log('--- turn 1 ---'); + const result = await agent.stream({ + session, + prompt: 'My name is Maya. Remember it.', + }); + await printFullStream({ result }); + resumeState = await session.stop(); + console.log('[stopped] resume state:', JSON.stringify(resumeState)); + } + + // Turn 2: brand-new agent instance, only the persisted state survives. + { + const agent = new HarnessAgent({ harness: grokBuild, sandbox }); + const session = await agent.createSession({ + sessionId, + resumeFrom: resumeState, + }); + console.log('--- turn 2 (resumed) ---'); + const result = await agent.stream({ + session, + prompt: 'What is my name? Answer in one word.', + }); + await printFullStream({ result }); + await session.destroy(); + } + + process.exit(0); +}); diff --git a/examples/ai-functions/src/harness-agent/grok-build/stream-text.ts b/examples/ai-functions/src/harness-agent/grok-build/stream-text.ts new file mode 100644 index 000000000000..e446a96666b4 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/grok-build/stream-text.ts @@ -0,0 +1,37 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; + +run(async () => { + const sandbox = createVercelSandbox({ + runtime: 'node24', + ports: [4000], + timeout: 10 * 60 * 1000, + }); + const agent = new HarnessAgent({ + harness: grokBuild, + sandbox, + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + const result = await agent.stream({ + session, + prompt: 'Recite the first sentence of "A Tale of Two Cities".', + }); + + await printFullStream({ result }); + + console.log('finishReason:', await result.finishReason); + console.log('usage:', await result.usage); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + process.exit(exitCode); + } +}); diff --git a/examples/ai-functions/src/harness-agent/grok-build/with-provided-sandbox.ts b/examples/ai-functions/src/harness-agent/grok-build/with-provided-sandbox.ts new file mode 100644 index 000000000000..2a77ee94cbde --- /dev/null +++ b/examples/ai-functions/src/harness-agent/grok-build/with-provided-sandbox.ts @@ -0,0 +1,40 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; +import { Sandbox } from '@vercel/sandbox'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; + +run(async () => { + const sandbox = await Sandbox.create({ + runtime: 'node24', + ports: [4000], + timeout: 10 * 60 * 1000, + }); + + const agent = new HarnessAgent({ + harness: grokBuild, + sandbox: createVercelSandbox({ sandbox }), + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + const result = await agent.stream({ + session, + prompt: 'In one sentence, what is the capital of France?', + }); + + await printFullStream({ result }); + + console.log('finishReason:', await result.finishReason); + console.log('usage:', await result.usage); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + await sandbox.stop().catch(() => {}); + process.exit(exitCode); + } +}); diff --git a/packages/harness-grok-build/README.md b/packages/harness-grok-build/README.md new file mode 100644 index 000000000000..d167ccb68311 --- /dev/null +++ b/packages/harness-grok-build/README.md @@ -0,0 +1,62 @@ +# AI SDK - Grok Build Harness + +`HarnessV1` adapter backed by the `grok` CLI (`@xai-official/grok`). The adapter ships a bridge process that runs inside a sandbox and talks to the host over a WebSocket on a sandbox-proxied loopback port. + +## Setup + +```bash +npm i @ai-sdk/harness-grok-build @ai-sdk/harness @ai-sdk/sandbox-vercel +``` + +The bridge installs the `grok` CLI inside the sandbox the first time the session starts. This requires network egress for the bootstrap install. + +## Usage + +```ts +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { grokBuild } from '@ai-sdk/harness-grok-build'; +import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; + +const agent = new HarnessAgent({ + harness: grokBuild, + sandbox: createVercelSandbox({ + runtime: 'node24', + ports: [4000], + }), +}); + +const session = await agent.createSession(); + +try { + const result = await agent.generate({ + session, + prompt: 'In one sentence, what is the capital of France?', + }); + console.log(result.text); +} finally { + await session.destroy(); +} +``` + +The adapter requires a `HarnessV1SandboxProvider` whose handles expose at least one TCP port — `@ai-sdk/sandbox-vercel` is the supported choice today. + +## Authentication + +Authentication is resolved from the host environment and forwarded to the sandbox bridge: + +- Direct: `XAI_API_KEY`. +- AI Gateway: `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`. + +The CLI maps these internally to `GROK_MODELS_BASE_URL` / `GROK_CODE_XAI_API_KEY`. + +## Limitations + +The grok CLI's `--output-format streaming-json` surface is narrow: + +- Streams reasoning and text only — no tool-call, tool-result, or file-change events, and no token usage. +- Allow-all permission mode only (`supportsBuiltinToolApprovals: false`); the CLI runs with `--always-approve` and executes tools itself. +- No compaction. + +## Related + +See the [AI SDK harness docs](https://ai-sdk.dev/docs/ai-sdk-harnesses) for sessions, tools, UI, and terminal usage. From 1b61e110ca5890f1f471b13eec1c0268a221d881 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 15:32:20 -0700 Subject: [PATCH 20/23] security bump --- packages/harness-grok-build/package.json | 2 +- packages/harness-grok-build/src/bridge/package.json | 2 +- packages/harness-grok-build/src/bridge/pnpm-lock.yaml | 10 +++++----- pnpm-lock.yaml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/harness-grok-build/package.json b/packages/harness-grok-build/package.json index e8d58c1d9424..e4c3902ad723 100644 --- a/packages/harness-grok-build/package.json +++ b/packages/harness-grok-build/package.json @@ -38,7 +38,7 @@ "dependencies": { "@ai-sdk/harness": "workspace:*", "@ai-sdk/provider-utils": "workspace:*", - "ws": "^8.20.1", + "ws": "^8.21.0", "zod": "3.25.76" }, "devDependencies": { diff --git a/packages/harness-grok-build/src/bridge/package.json b/packages/harness-grok-build/src/bridge/package.json index 905a3be13db4..d47b6d2397bc 100644 --- a/packages/harness-grok-build/src/bridge/package.json +++ b/packages/harness-grok-build/src/bridge/package.json @@ -5,7 +5,7 @@ "type": "module", "dependencies": { "@xai-official/grok": "0.2.51", - "ws": "8.20.1", + "ws": "8.21.0", "zod": "3.25.76" } } diff --git a/packages/harness-grok-build/src/bridge/pnpm-lock.yaml b/packages/harness-grok-build/src/bridge/pnpm-lock.yaml index e8879ac67039..7fe70027a50e 100644 --- a/packages/harness-grok-build/src/bridge/pnpm-lock.yaml +++ b/packages/harness-grok-build/src/bridge/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: 0.2.51 version: 0.2.51 ws: - specifier: 8.20.1 - version: 8.20.1 + specifier: 8.21.0 + version: 8.21.0 zod: specifier: 3.25.76 version: 3.25.76 @@ -60,8 +60,8 @@ packages: os: [darwin, linux, win32] hasBin: true - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -108,6 +108,6 @@ snapshots: '@xai-official/grok-win32-arm64': 0.2.51 '@xai-official/grok-win32-x64': 0.2.51 - ws@8.20.1: {} + ws@8.21.0: {} zod@3.25.76: {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77ed8384f150..c69c9c14a307 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2615,7 +2615,7 @@ importers: specifier: workspace:* version: link:../provider-utils ws: - specifier: ^8.20.1 + specifier: ^8.21.0 version: 8.21.0 zod: specifier: 3.25.76 From 0c12f4ca38345c525669d685cbfd262274b20280 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 17:11:00 -0700 Subject: [PATCH 21/23] refactor(harness-grok-build): remove debug option and enhance gateway URL handling --- .../agent/harness/grok-build/basic-agent.ts | 1 - examples/harness-e2e-tui/agents/grok-build/basic-agent.ts | 1 - packages/harness-grok-build/src/grok-build-auth.test.ts | 8 ++++++++ packages/harness-grok-build/src/grok-build-auth.ts | 8 +++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts b/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts index f4f088ff6128..b0eed3a1658d 100644 --- a/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts +++ b/examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts @@ -13,7 +13,6 @@ export const grokBuildHarnessAgent = new HarnessAgent({ runtime: 'node24', ports: [4000], }), - debug: { enabled: true }, telemetry: { integrations: [ createTraceTreeReporter(), diff --git a/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts b/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts index f4f088ff6128..b0eed3a1658d 100644 --- a/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts +++ b/examples/harness-e2e-tui/agents/grok-build/basic-agent.ts @@ -13,7 +13,6 @@ export const grokBuildHarnessAgent = new HarnessAgent({ runtime: 'node24', ports: [4000], }), - debug: { enabled: true }, telemetry: { integrations: [ createTraceTreeReporter(), diff --git a/packages/harness-grok-build/src/grok-build-auth.test.ts b/packages/harness-grok-build/src/grok-build-auth.test.ts index da662d9aac42..b0b6ee7b23e9 100644 --- a/packages/harness-grok-build/src/grok-build-auth.test.ts +++ b/packages/harness-grok-build/src/grok-build-auth.test.ts @@ -64,4 +64,12 @@ describe('toGrokCliEnv', () => { // The direct xAI var must not leak when routing through the gateway. expect(cliEnv.XAI_API_KEY).toBeUndefined(); }); + + it('appends /v1 to the gateway base url when missing', () => { + const resolved = resolveGrokBuildEnv(undefined, { + AI_GATEWAY_API_KEY: 'gw-key', + }); + const cliEnv = toGrokCliEnv(resolved); + expect(cliEnv.GROK_MODELS_BASE_URL).toBe('https://ai-gateway.vercel.sh/v1'); + }); }); diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts index 7bac2bfe2001..cdf8717ecb1c 100644 --- a/packages/harness-grok-build/src/grok-build-auth.ts +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -49,7 +49,8 @@ export function toGrokCliEnv( const key = resolved.AI_GATEWAY_API_KEY; const baseUrl = resolved.AI_GATEWAY_BASE_URL ?? resolved.XAI_BASE_URL; if (key) env.GROK_CODE_XAI_API_KEY = key; - if (baseUrl) env.GROK_MODELS_BASE_URL = baseUrl; + // grok's GROK_MODELS_BASE_URL must point at the gateway's `/v1` endpoint. + if (baseUrl) env.GROK_MODELS_BASE_URL = toGatewayV1BaseUrl(baseUrl); return env; } const env: Record = {}; @@ -58,6 +59,11 @@ export function toGrokCliEnv( return env; } +function toGatewayV1BaseUrl(baseUrl: string): string { + const trimmed = baseUrl.replace(/\/+$/, ''); + return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`; +} + function pickXai( explicit: NonNullable, processEnv: Record, From b1e71ca946c8a39eaf69b4f449119011ddc4fd80 Mon Sep 17 00:00:00 2001 From: mlekhi Date: Thu, 18 Jun 2026 17:27:10 -0700 Subject: [PATCH 22/23] adding attach/resume --- .../harness-grok-build/src/grok-build-auth.ts | 1 - .../src/grok-build-harness.test.ts | 77 ++++++++++++- .../src/grok-build-harness.ts | 107 +++++++++++++++--- 3 files changed, 167 insertions(+), 18 deletions(-) diff --git a/packages/harness-grok-build/src/grok-build-auth.ts b/packages/harness-grok-build/src/grok-build-auth.ts index cdf8717ecb1c..d07e2008d08a 100644 --- a/packages/harness-grok-build/src/grok-build-auth.ts +++ b/packages/harness-grok-build/src/grok-build-auth.ts @@ -39,7 +39,6 @@ export function resolveGrokBuildEnv( } // Translate the resolved auth blob into the env vars the grok CLI reads. -// Direct: XAI_API_KEY. Gateway (keyed off AI_GATEWAY_API_KEY): GROK_MODELS_BASE_URL + GROK_CODE_XAI_API_KEY. export function toGrokCliEnv( resolved: Record, ): Record { diff --git a/packages/harness-grok-build/src/grok-build-harness.test.ts b/packages/harness-grok-build/src/grok-build-harness.test.ts index d1675be3f17a..f1e76163bb7c 100644 --- a/packages/harness-grok-build/src/grok-build-harness.test.ts +++ b/packages/harness-grok-build/src/grok-build-harness.test.ts @@ -7,11 +7,14 @@ import type * as NodeFsPromises from 'node:fs/promises'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const sentMessages: Array> = []; +const openCalls: Array<{ resume?: boolean } | undefined> = []; vi.mock('@ai-sdk/harness/utils', async importOriginal => { const actual = await importOriginal(); class FakeSandboxChannel { - async open(): Promise {} + async open(opts?: { resume?: boolean }): Promise { + openCalls.push(opts); + } on(): () => void { return () => {}; } @@ -140,6 +143,7 @@ describe('grok-build bootstrap', () => { describe('grok-build doStart', () => { beforeEach(() => { sentMessages.length = 0; + openCalls.length = 0; }); afterEach(() => { vi.restoreAllMocks(); @@ -291,6 +295,77 @@ describe('grok-build doStart', () => { expect(session.isResume).toBe(true); }); + it('attaches (no spawn) when resumeFrom carries live bridge coords', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + const session = await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + resumeFrom: { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { + sessionId: 'grok-sess-123', + bridge: { + port: 4319, + token: 'tok-abc', + lastSeenEventId: 7, + }, + }, + }, + }); + expect(spawnCalls).toHaveLength(0); + expect(openCalls).toEqual([{ resume: true }]); + expect(session.isResume).toBe(true); + }); + + it('spawns (fresh path) when resumeFrom has a session id but no bridge coords', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + resumeFrom: { + type: 'resume-session', + harnessId: 'grok-build', + specificationVersion: 'harness-v1', + data: { sessionId: 'grok-sess-123' }, + }, + }); + expect(spawnCalls).toHaveLength(1); + expect(openCalls).toEqual([undefined]); + }); + + it('spawns the bridge on a fresh start with no resumeFrom', async () => { + const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); + const spawnCalls: Array<{ + command: string; + env: Record; + }> = []; + const runs: string[] = []; + await harness.doStart({ + sessionId: 's1', + sandboxSession: fakeSandbox({ spawnCalls, runs }), + sessionWorkDir: '/vercel/sandbox/grok-s1', + permissionMode: 'allow-all', + }); + expect(spawnCalls).toHaveLength(1); + expect(openCalls).toEqual([undefined]); + }); + it('continues the grok thread on the first prompt after resume, then stops', async () => { const harness = createGrokBuild({ auth: { xai: { apiKey: 'sk' } } }); const spawnCalls: Array<{ diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 089b766bc0cd..4c1ef5b75f72 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -158,6 +158,11 @@ const lifecycleStateSchema = z.object({ .optional(), }); +// Live bridge coordinates for cross-process attach (present on detach/suspend payloads). +type GrokBridgeCoords = NonNullable< + z.infer['bridge'] +>; + async function readBridgeAsset(name: string): Promise { const candidates = [ new URL(`./bridge/${name}`, import.meta.url), @@ -235,6 +240,7 @@ export function createGrokBuild( isResume && typeof lifecycleState?.data === 'object' ? (lifecycleState.data as { sessionId?: unknown; + bridge?: GrokBridgeCoords; }) : undefined; const resumeSessionId = @@ -242,6 +248,7 @@ export function createGrokBuild( resumeData.sessionId.length > 0 ? resumeData.sessionId : undefined; + const coords = resumeData?.bridge; const workDir = startOpts.sessionWorkDir; const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`; @@ -260,14 +267,44 @@ export function createGrokBuild( ) : undefined; - // Resolve auth → concrete grok CLI env vars, and pick the matching model id. + // Resolve auth → grok CLI env vars + the matching model id (direct vs gateway). const resolvedAuth = resolveGrokBuildEnv(settings.auth); - const grokEnv = toGrokCliEnv(resolvedAuth); - const isGateway = resolvedAuth.AI_GATEWAY_API_KEY != null; const model = settings.model ?? - (isGateway ? DEFAULT_GROK_MODEL_GATEWAY : DEFAULT_GROK_MODEL_DIRECT); + (resolvedAuth.AI_GATEWAY_API_KEY != null + ? DEFAULT_GROK_MODEL_GATEWAY + : DEFAULT_GROK_MODEL_DIRECT); + + // Attach: reopen a socket to the still-running bridge instead of respawning. + if (coords) { + const attachUrl = + (await sandboxSession.getPortUrl({ + port: coords.port, + protocol: 'ws', + })) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`; + const attachChannel: GrokBuildChannel = new SandboxChannel({ + connect: () => openWebSocket(attachUrl), + outboundSchema: outboundMessageSchema, + initialLastSeenEventId: coords.lastSeenEventId, + onDiagnostic, + }); + await attachChannel.open({ resume: true }); + return createSession({ + sessionId: startOpts.sessionId, + channel: attachChannel, + proc: undefined, // live bridge owned by another process + model, + isResume: true, + bridgePort: coords.port, + bridgeToken: coords.token, + sandboxId, + debug: startOpts.observability?.debug, + permissionMode: startOpts.permissionMode, + resumeGrokSessionId: resumeSessionId, + }); + } + const grokEnv = toGrokCliEnv(resolvedAuth); const port = resolveBridgePort(sandboxSession, settings.port); const token = randomBytes(32).toString('hex'); const env = { @@ -294,6 +331,34 @@ export function createGrokBuild( abortSignal: startOpts.abortSignal, }); + // Collect bridge stderr from spawn so a startup failure is diagnosable. + const startupStderr: string[] = []; + const stderrDone = forwardBridgeStderr( + proc.stderr, + startOpts.observability?.debug, + startupStderr, + ); + const withTail = async ( + message: string, + ctx: { stdoutTail: string[] }, + ): Promise => { + // Bounded waits so the timeout path (stream still open) never hangs. + await raceTimeout(stderrDone, 1000); + const result = (await raceTimeout(proc.wait(), 250)) as + | { exitCode?: number } + | undefined; + const exit = + result?.exitCode != null ? ` Exit code: ${result.exitCode}.` : ''; + const parts = [`${message}${exit}`]; + if (startupStderr.length > 0) { + parts.push(`stderr:\n${startupStderr.join('\n')}`); + } + if (ctx.stdoutTail.length > 0) { + parts.push(`stdout:\n${ctx.stdoutTail.join('\n')}`); + } + return new Error(parts.join('\n\n')); + }; + const { port: boundPort } = await waitForBridgeReady({ proc, sandbox: session, @@ -301,13 +366,12 @@ export function createGrokBuild( bridgeType: 'grok-build', timeoutMs, abortSignal: startOpts.abortSignal, - createTimeoutError: () => - new Error('grok-build bridge did not become ready in time.'), - createExitError: () => - new Error('grok-build bridge exited before becoming ready.'), + createTimeoutError: ctx => + withTail('grok-build bridge did not become ready in time.', ctx), + createExitError: ctx => + withTail('grok-build bridge exited before becoming ready.', ctx), }); void drainRest(proc.stdout); - void forwardBridgeStderr(proc.stderr, startOpts.observability?.debug); const wsUrl = (await sandboxSession.getPortUrl({ @@ -374,15 +438,20 @@ function openWebSocket(url: string): Promise { }); } -// Live-forward bridge stderr to the terminal only under debug. grok logs -// recoverable errors (e.g. a spurious `grok-build` model probe that 404s) to -// stderr mid-turn; printing those by default paints over TUI consumers. Fatal -// exits still surface their stderr tail via the bridge's close handler. +// Resolve a promise but give up after `ms`, swallowing rejections. +function raceTimeout(p: PromiseLike, ms: number): Promise { + return Promise.race([ + Promise.resolve(p).catch(() => undefined), + new Promise(resolve => setTimeout(resolve, ms)), + ]); +} + +// Collect a stderr tail for startup diagnostics; echo to terminal only under debug. async function forwardBridgeStderr( stream: ReadableStream, debug: HarnessV1DebugConfig | undefined, + collectTail?: string[], ): Promise { - if (debug?.enabled !== true) return; try { const reader = stream.pipeThrough(new TextDecoderStream()).getReader(); while (true) { @@ -391,8 +460,14 @@ async function forwardBridgeStderr( if (value) { const trimmed = value.endsWith('\n') ? value.slice(0, -1) : value; if (trimmed.length > 0) { - // eslint-disable-next-line no-console - console.log(`[bridge stderr] ${trimmed}`); + if (collectTail) { + collectTail.push(trimmed); + if (collectTail.length > 20) collectTail.shift(); + } + if (debug?.enabled === true) { + // eslint-disable-next-line no-console + console.log(`[bridge stderr] ${trimmed}`); + } } } } From 4c5c7386ed5107645aef15aed69db5352da922cc Mon Sep 17 00:00:00 2001 From: mlekhi Date: Mon, 29 Jun 2026 15:22:39 -0700 Subject: [PATCH 23/23] cutting planning --- content/providers/02-ai-sdk-harnesses/04-grok-build.mdx | 2 -- .../harness-grok-build/src/grok-build-bridge-protocol.test.ts | 3 +-- packages/harness-grok-build/src/grok-build-bridge-protocol.ts | 1 - packages/harness-grok-build/src/grok-build-harness.ts | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/content/providers/02-ai-sdk-harnesses/04-grok-build.mdx b/content/providers/02-ai-sdk-harnesses/04-grok-build.mdx index 3e1c7e3dd517..92769c414ddd 100644 --- a/content/providers/02-ai-sdk-harnesses/04-grok-build.mdx +++ b/content/providers/02-ai-sdk-harnesses/04-grok-build.mdx @@ -104,7 +104,6 @@ Use `createGrokBuild()` to configure the runtime: ```ts const harness = createGrokBuild({ model: 'grok-code-fast-1', - planMode: true, }); ``` @@ -112,7 +111,6 @@ Settings: - `auth`: xAI or AI Gateway authentication settings. - `model`: Grok model id. If omitted, the adapter uses its pinned default. -- `planMode`: run the CLI in plan mode. - `port`: bridge port override. - `startupTimeoutMs`: maximum time to wait for the bridge to start. diff --git a/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts b/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts index ccc235b5b4da..344b512270a2 100644 --- a/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts +++ b/packages/harness-grok-build/src/grok-build-bridge-protocol.test.ts @@ -15,11 +15,10 @@ describe('grok-build bridge protocol', () => { type: 'start', prompt: 'hi', model: 'grok-build-0.1', - planMode: true, continue: true, }); expect(parsed.model).toBe('grok-build-0.1'); - expect(parsed.planMode).toBe(true); + expect(parsed.continue).toBe(true); }); it('discriminates start within the inbound union', () => { diff --git a/packages/harness-grok-build/src/grok-build-bridge-protocol.ts b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts index 5a4c3151cc54..85f42e287710 100644 --- a/packages/harness-grok-build/src/grok-build-bridge-protocol.ts +++ b/packages/harness-grok-build/src/grok-build-bridge-protocol.ts @@ -12,7 +12,6 @@ export type OutboundMessage = z.infer; export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({ model: z.string().optional(), - planMode: z.boolean().optional(), // Resume the prior CLI thread instead of a fresh session. continue: z.boolean().optional(), }); diff --git a/packages/harness-grok-build/src/grok-build-harness.ts b/packages/harness-grok-build/src/grok-build-harness.ts index 4c1ef5b75f72..bfa1d1e75769 100644 --- a/packages/harness-grok-build/src/grok-build-harness.ts +++ b/packages/harness-grok-build/src/grok-build-harness.ts @@ -138,7 +138,6 @@ const DEFAULT_GROK_MODEL_GATEWAY = 'xai/grok-build-0.1'; export type GrokBuildHarnessSettings = { readonly model?: string; - readonly planMode?: boolean; readonly auth?: GrokBuildAuthOptions; readonly port?: number; /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */