Introduce a first-class groq provider branch with normalized defaults, Groq-compatible request shaping (parallel_tool_calls + max_completion_tokens), and tool-delta diagnostics, while keeping xAI behavior intact. Also gate xai-compat skill seeding to provider: xai, update docs, and extend workspace/config surface tests for provider-aware behavior.

This commit is contained in:
Raven Scott
2026-04-26 05:04:25 -04:00
parent f5e9473f7b
commit 0893938b16
18 changed files with 152 additions and 12 deletions
+18
View File
@@ -5535,6 +5535,15 @@ function bareAgentApplyProviderProfile(cfg) {
const provider = String(out.provider || '').trim().toLowerCase()
const model = String(out.model || '').trim().toLowerCase()
const defaultGroq = 'https://api.groq.com/openai/v1'
if (provider === 'groq') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (cur < 120000) out.request_timeout_ms = 120000
}
if (provider === 'xai') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1'
@@ -5743,6 +5752,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = bareAgentApplyProviderProfile(config)
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
@@ -5956,6 +5966,11 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (providerNow === 'groq') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') {
bareAgentWriteOut(
ctx,
@@ -6040,6 +6055,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress('warning tool_calls_finish_without_tool_deltas provider=' + providerNow)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T08:56:11.752Z",
"generatedAt": "2026-04-26T09:01:31.590Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777193771751,
"atMs": 1777194091590,
"commands": [
"agent",
"arch",
@@ -40,6 +40,11 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
- `xai` profile defaults to `https://api.x.ai/v1` with reasoning-summary/event compatibility handling.
## Editing
1. Change files under **`~/.agent/workspace/`** on your **personal** drive.
+2
View File
@@ -22,6 +22,8 @@
- `reasoning_include_tools` (include tool call/result traces in process output)
- Use `edit_agent_config` to toggle these safely during a session.
- For provider `xai`, keep `rest_base_url` at `https://api.x.ai/v1` and prefer trace mode when reasoning summaries are unavailable.
- For provider `groq`, keep `rest_base_url` at `https://api.groq.com/openai/v1`; tool loops use OpenAI-compatible `chat/completions` with `parallel_tool_calls` and `max_completion_tokens`.
- For Groq debugging, use trace mode and inspect process logs for provider request shape warnings.
## Max-autonomy policy toggles
File diff suppressed because one or more lines are too long
+20 -2
View File
@@ -281,6 +281,15 @@ function bareAgentApplyProviderProfile(cfg) {
const provider = String(out.provider || '').trim().toLowerCase()
const model = String(out.model || '').trim().toLowerCase()
const defaultGroq = 'https://api.groq.com/openai/v1'
if (provider === 'groq') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (cur < 120000) out.request_timeout_ms = 120000
}
if (provider === 'xai') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1'
@@ -461,7 +470,7 @@ async function bareAgentRunSetupOnly(ctx, argv0) {
}
try {
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
} catch {
bareAgentErr(
@@ -489,6 +498,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = bareAgentApplyProviderProfile(config)
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
@@ -562,7 +572,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const manDigest = await bareAgentManDigest(ctx)
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
const workspacePromptBlock = await bareAgentLoadWorkspacePrompt(
ctx,
@@ -702,6 +712,11 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (providerNow === 'groq') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') {
bareAgentWriteOut(
ctx,
@@ -786,6 +801,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress('warning tool_calls_finish_without_tool_deltas provider=' + providerNow)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
@@ -117,8 +117,9 @@ async function bareAgentEnsureWorkspace(ctx, paths) {
* Ensure workspace/skills templates and ~/.agent/skill-loader.js exist (idempotent; for upgrades).
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string, workspaceSkills: string, dir: string }} paths
* @param {Record<string, unknown>} [config]
*/
async function bareAgentEnsureSkillTemplates(ctx, paths) {
async function bareAgentEnsureSkillTemplates(ctx, paths, config) {
const vfs = ctx.vfs
if (
!vfs ||
@@ -127,6 +128,8 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
typeof vfs.mkdir !== 'function'
)
return
const provider =
config && typeof config === 'object' ? String(config.provider || '').trim().toLowerCase() : ''
try {
await vfs.mkdir(paths.workspaceSkills, { recursive: true })
} catch {
@@ -134,6 +137,16 @@ async function bareAgentEnsureSkillTemplates(ctx, paths) {
}
const share = BARE_AGENT_WORKSPACE_SHARE
for (const rel of BARE_AGENT_SKILL_SEED_REL) {
if (rel === 'skills/xai-compat/SKILL.md' && provider !== 'xai') {
if (typeof vfs.unlink === 'function') {
try {
await vfs.unlink(paths.workspace + '/' + rel)
} catch {
/* ignore */
}
}
continue
}
const dest = paths.workspace + '/' + rel
try {
const b = await vfs.readFile(dest)
@@ -22,7 +22,7 @@ This tree follows the **agent** Markdown workspace convention: “soul” files
3. During a session, the model loads the full document with the **`read_skill`** tool (do not paste huge skills into the user channel unless asked).
4. Shared skills can live under **`~/.agent/skills/`**; keep **`workspace/skills/`** for machine-local or repo-specific behavior.
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`xai-compat`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair).
Seeded examples in this repo (under **`skills/`**): **`p2p-os-status`**, **`bare-os-kernel-proc`**, **`bare-os-super-developer`**, **`agent-ops`**, **`holesail`** (managed **`state.json`**, **`seed`**/**`key`**, stock **`bare-www-*`** / **`bare-ssh-*`**), and **`hdms`** (Hyperdrive mounts and invite/pair). Provider-specific skill **`xai-compat`** is only seeded when `provider` is configured as `xai`.
After **`agent --config`** / **`--setup`** (or changing **`owner_name`** / **`agent_label`** via **`edit_agent_config`**), **`IDENTITY.md`** and **`USER.md`** are regenerated from **`config.json`** so the workspace matches the operator and agent label.
@@ -40,6 +40,11 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
- `xai` profile defaults to `https://api.x.ai/v1` with reasoning-summary/event compatibility handling.
## Editing
1. Change files under **`~/.agent/workspace/`** on your **personal** drive.
@@ -22,6 +22,8 @@
- `reasoning_include_tools` (include tool call/result traces in process output)
- Use `edit_agent_config` to toggle these safely during a session.
- For provider `xai`, keep `rest_base_url` at `https://api.x.ai/v1` and prefer trace mode when reasoning summaries are unavailable.
- For provider `groq`, keep `rest_base_url` at `https://api.groq.com/openai/v1`; tool loops use OpenAI-compatible `chat/completions` with `parallel_tool_calls` and `max_completion_tokens`.
- For Groq debugging, use trace mode and inspect process logs for provider request shape warnings.
## Max-autonomy policy toggles
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs'
const STATE = readFileSync(new URL('../lib/agent-state.js', import.meta.url), 'utf8')
const TOOLS = readFileSync(new URL('../lib/agent-tools.js', import.meta.url), 'utf8')
const OPENAI = readFileSync(new URL('../lib/agent-openai.js', import.meta.url), 'utf8')
const TUI = readFileSync(new URL('../lib/agent-tui.js', import.meta.url), 'utf8')
test('agent-state exposes reasoning config keys', async (t) => {
for (const key of [
@@ -61,3 +62,10 @@ test('agent-openai parses reasoning deltas when present', async (t) => {
t.ok(OPENAI.includes('response.reasoning_summary_text.delta'))
t.ok(OPENAI.includes('response_shape_keys'))
})
test('agent-tui contains groq provider profile/request shaping', async (t) => {
t.ok(TUI.includes("provider === 'groq'"))
t.ok(TUI.includes('https://api.groq.com/openai/v1'))
t.ok(TUI.includes('body.parallel_tool_calls'))
t.ok(TUI.includes('body.max_completion_tokens'))
})
@@ -109,7 +109,7 @@ test('bareAgentEnsureWorkspace seeds from share', async (t) => {
t.ok(written.some(([p]) => p === '/home/x/.agent/skill-loader.js'))
})
test('bareAgentEnsureSkillTemplates copies skill seeds when missing', async (t) => {
test('bareAgentEnsureSkillTemplates copies skill seeds when missing for xai provider', async (t) => {
const written = []
const files = new Map()
const set = (p, txt) => files.set(p, txt)
@@ -145,7 +145,8 @@ test('bareAgentEnsureSkillTemplates copies skill seeds when missing', async (t)
dir: '/home/x/.agent',
workspace: '/home/x/.agent/workspace',
workspaceSkills: '/home/x/.agent/workspace/skills'
}
},
{ provider: 'xai' }
)
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/p2p-os-status/SKILL.md'))
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/bare-os-kernel-proc/SKILL.md'))
@@ -157,6 +158,49 @@ test('bareAgentEnsureSkillTemplates copies skill seeds when missing', async (t)
t.ok(written.some(([p]) => p === '/home/x/.agent/skill-loader.js'))
})
test('bareAgentEnsureSkillTemplates skips xai skill for non-xai provider', async (t) => {
const written = []
const files = new Map()
const set = (p, txt) => files.set(p, txt)
set('/share/agent-workspace/skills/.gitkeep', '')
set('/share/agent-workspace/skills/p2p-os-status/SKILL.md', '# Skill')
set('/share/agent-workspace/skills/bare-os-kernel-proc/SKILL.md', '# Proc skill')
set('/share/agent-workspace/skills/bare-os-super-developer/SKILL.md', '# Dev skill')
set('/share/agent-workspace/skills/agent-ops/SKILL.md', '# Ops skill')
set('/share/agent-workspace/skills/xai-compat/SKILL.md', '# xAI skill')
set('/share/agent-workspace/skills/holesail/SKILL.md', '# Holesail skill')
set('/share/agent-workspace/skills/hdms/SKILL.md', '# Hdms skill')
set('/share/agent-workspace/skill-loader.stub.js', '// stub')
const vfs = {
async mkdir(_p, _o) {},
async readFile(p) {
if (files.has(p)) return new TextEncoder().encode(files.get(p))
throw new Error('enoent')
},
async writeFile(p, buf) {
const txt =
buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf)
written.push([p, txt])
files.set(p, txt)
}
}
const s = load()
const fn = /** @type {(ctx: object, paths: object, cfg?: object) => Promise<void>} */ (
s.bareAgentEnsureSkillTemplates
)
await fn(
{ vfs, b4a: null },
{
dir: '/home/x/.agent',
workspace: '/home/x/.agent/workspace',
workspaceSkills: '/home/x/.agent/workspace/skills'
},
{ provider: 'groq' }
)
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/p2p-os-status/SKILL.md'))
t.ok(!written.some(([p]) => p === '/home/x/.agent/workspace/skills/xai-compat/SKILL.md'))
})
test('bareAgentSyncWorkspaceFromConfig writes IDENTITY and USER from config', async (t) => {
const written = []
const vfs = {
+18
View File
@@ -5535,6 +5535,15 @@ function bareAgentApplyProviderProfile(cfg) {
const provider = String(out.provider || '').trim().toLowerCase()
const model = String(out.model || '').trim().toLowerCase()
const defaultGroq = 'https://api.groq.com/openai/v1'
if (provider === 'groq') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (cur < 120000) out.request_timeout_ms = 120000
}
if (provider === 'xai') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1'
@@ -5743,6 +5752,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = bareAgentApplyProviderProfile(config)
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
@@ -5956,6 +5966,11 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (providerNow === 'groq') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') {
bareAgentWriteOut(
ctx,
@@ -6040,6 +6055,9 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress('warning tool_calls_finish_without_tool_deltas provider=' + providerNow)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T08:56:11.752Z",
"generatedAt": "2026-04-26T09:01:31.590Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777193771751,
"atMs": 1777194091590,
"commands": [
"agent",
"arch",
@@ -40,6 +40,11 @@ Max-autonomy bridge policy switches are also in `config.json`:
- `allow_host_actions` — host action route gate
- `emergency_stop_mutations` — kill switch for mutating bridge tools
Provider profile notes:
- `groq` profile defaults to `https://api.groq.com/openai/v1` and OpenAI-compatible chat completions/tool calling semantics.
- `xai` profile defaults to `https://api.x.ai/v1` with reasoning-summary/event compatibility handling.
## Editing
1. Change files under **`~/.agent/workspace/`** on your **personal** drive.
@@ -22,6 +22,8 @@
- `reasoning_include_tools` (include tool call/result traces in process output)
- Use `edit_agent_config` to toggle these safely during a session.
- For provider `xai`, keep `rest_base_url` at `https://api.x.ai/v1` and prefer trace mode when reasoning summaries are unavailable.
- For provider `groq`, keep `rest_base_url` at `https://api.groq.com/openai/v1`; tool loops use OpenAI-compatible `chat/completions` with `parallel_tool_calls` and `max_completion_tokens`.
- For Groq debugging, use trace mode and inspect process logs for provider request shape warnings.
## Max-autonomy policy toggles
File diff suppressed because one or more lines are too long