Skip to content
Draft
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9141f62
chore(harness-grok-build): scaffold package and build configs
mlekhi Jun 17, 2026
325cb05
chore(harness-grok-build): add root tsconfig reference and grok devDe…
mlekhi Jun 17, 2026
1217577
feat(harness-grok-build): add auth resolver
mlekhi Jun 17, 2026
9506844
refactor(harness-grok-build): always forward gateway base url in auth…
mlekhi Jun 17, 2026
a4348d1
feat(harness-grok-build): add bridge protocol start schema
mlekhi Jun 17, 2026
69ee112
Add GROK_BUILD_BUILTIN_TOOLS (read/write/edit/bash/glob/grep/webSearch)
mlekhi Jun 17, 2026
c7f7127
feat(harness-grok-build): add createGrokBuild factory and bootstrap r…
mlekhi Jun 17, 2026
918cd4c
feat(harness-grok-build): map streaming-json output to harness stream…
mlekhi Jun 18, 2026
9096156
add bridge turn driver and doStart
mlekhi Jun 18, 2026
ad747c6
add local generate-text smoke test
mlekhi Jun 18, 2026
692088b
adding vercel sandbox to smoke test
mlekhi Jun 18, 2026
a4e6cf6
setting changeset to major
mlekhi Jun 18, 2026
0c67b1a
cutting
mlekhi Jun 18, 2026
604a372
adding robustness + cleaning
mlekhi Jun 18, 2026
bec5953
wire into e2e-tui and e2e-next apps
mlekhi Jun 18, 2026
d7473b0
feat(harness-grok-build): conditionally forward bridge stderr based o…
mlekhi Jun 18, 2026
45d2b83
fix(harness-grok-build): continue thread on resume and apply turn ins…
mlekhi Jun 18, 2026
f23f83b
adding changelog
mlekhi Jun 18, 2026
65439bb
docs(harness-grok-build): add README, provider docs, and examples
mlekhi Jun 18, 2026
1b61e11
security bump
mlekhi Jun 18, 2026
0c12f4c
refactor(harness-grok-build): remove debug option and enhance gateway…
mlekhi Jun 19, 2026
b1e71ca
adding attach/resume
mlekhi Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/grok-build-turn-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/harness-grok-build': major
---

feat(harness-grok-build): implement bridge turn driver and doStart
1 change: 1 addition & 0 deletions examples/ai-functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { grokBuild } from '@ai-sdk/harness-grok-build';
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
import { run } from '../../lib/run';

// 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,
});

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);
}
});
Original file line number Diff line number Diff line change
@@ -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<typeof aiSdkCodingGrokBuildHarnessAgent.tools>
>;
29 changes: 29 additions & 0 deletions examples/harness-e2e-next/agent/harness/grok-build/basic-agent.ts
Original file line number Diff line number Diff line change
@@ -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<typeof grokBuildHarnessAgent.tools>
>;
Original file line number Diff line number Diff line change
@@ -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),
}),
});
}
Original file line number Diff line number Diff line change
@@ -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),
}),
});
}
Original file line number Diff line number Diff line change
@@ -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),
}),
});
}
Original file line number Diff line number Diff line change
@@ -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 (
<ChatIdProvider storageKey={STORAGE_KEY}>
<GrokBuildHarnessChat
apiRoute="/api/harness/grok-build/ai-sdk-coding"
exampleLabel="AI SDK Checkout"
/>
</ChatIdProvider>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<ChatIdProvider storageKey={STORAGE_KEY}>
<GrokBuildHarnessChat
apiRoute="/api/harness/grok-build/basic-with-stop"
exampleLabel="Basic (with stop)"
/>
</ChatIdProvider>
);
}
19 changes: 19 additions & 0 deletions examples/harness-e2e-next/app/harness/grok-build/basic/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ChatIdProvider storageKey={STORAGE_KEY}>
<GrokBuildHarnessChat
apiRoute="/api/harness/grok-build/basic"
exampleLabel="Basic"
/>
</ChatIdProvider>
);
}
5 changes: 5 additions & 0 deletions examples/harness-e2e-next/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = Object.fromEntries(
Expand Down
Loading
Loading