mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(grok): deliver intake draft via the relay + correct opencode-mechanism docs
Live end-to-end verification (opencode 1.17.8 + grok-build-0.1) of the WHOLE
integration, then fixes for what it surfaced:
1) Intake draft card (FUNCTIONAL): opencode's synchronous serve reply
(POST /session/:id/message) returns only [step-start, text, step-finish] — it
does NOT include tool-call parts, so the driver could never extract the
propose_draft draft. intake-tools.js now POSTs the draft straight to the
prompter-live relay (/api/prompter/live/{session}/events, the same endpoint
the driver's relay sink uses), so the panel renders the card regardless.
Verified live: grok calls propose_draft -> the relay receives the draft.
2) Correct misattributed opencode "bugs" (DOCS): earlier comments asserted as
general opencode behavior that a provider.xai block / npm override / config
plugin:-array "break" registration. Re-testing showed those were artifacts of
a PROJECT-level .opencode/opencode.json; from the GLOBAL config (which
opencode_config writes) the built-in provider, model resolution, the plugin
array AND the auto-discovery dir all work, and MCP gateway verbs register
(delivery agents verified). Reframed the comments as design choices (built-in
provider + XAI_API_KEY env + plugins baked in the auto-discovery dir with
named exports) and dropped the false claims.
3) Reasoning --variant: passing it does not error, but whether opencode applies a
named reasoning variant to grok-build-0.1 (no provider-defined variants) is
UNVERIFIED — comment softened from a "~54% cut" claim to best-effort,
measure-on-NAS.
Verified live this session: one-shot delivery (model + MCP verbs + plugins +
hooks), secretary tools (read_company_state + submit_directive -> backend with
token), intake draft (relay), grok built-in-provider tool-calling. Remaining
NAS-only: full container assembly (SDK :9000 startup, entrypoint hooks, 429
parking) + the --variant cost measurement. Gate green (ruff/mypy + 51 tests;
node --check the plugins).
This commit is contained in:
@@ -13,16 +13,15 @@ FROM roboco-agent-base
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
# opencode — the OpenAI-protocol agent runtime. grok-build-0.1 runs on opencode's
|
# opencode — the OpenAI-protocol agent runtime. grok-build-0.1 runs on opencode's
|
||||||
# BUILT-IN xai provider (no custom provider npm — that breaks model resolution),
|
# BUILT-IN xai provider (no custom provider block / npm needed), so only
|
||||||
# so only opencode-ai is installed; it resolves the provider SDK at runtime.
|
# opencode-ai is installed; it resolves the provider SDK at runtime.
|
||||||
RUN npm install -g opencode-ai \
|
RUN npm install -g opencode-ai \
|
||||||
&& npm cache clean --force \
|
&& npm cache clean --force \
|
||||||
&& rm -rf /root/.npm /tmp/*
|
&& rm -rf /root/.npm /tmp/*
|
||||||
|
|
||||||
# opencode plugins, baked into the AUTO-DISCOVERY dir (~/.config/opencode/plugin/).
|
# opencode plugins, baked into the AUTO-DISCOVERY dir (~/.config/opencode/plugin/)
|
||||||
# opencode 1.17.8 does NOT register a plugin's hooks/tools from a config
|
# rather than referenced by a config `plugin:` path — the dir is the simplest
|
||||||
# `plugin:`-array absolute path — only from this directory (verified live). Each
|
# registration route. Each plugin uses a NAMED export (opencode's convention).
|
||||||
# plugin uses a NAMED export.
|
|
||||||
# secret-scrub — bash-guard parity (PAT/credential deny on tool.execute.before)
|
# secret-scrub — bash-guard parity (PAT/credential deny on tool.execute.before)
|
||||||
# budget-feed — POSTs budget/loop/terminal counters to the in-container SDK
|
# budget-feed — POSTs budget/loop/terminal counters to the in-container SDK
|
||||||
# server (tool.execute.{before,after}); the entrypoint starts
|
# server (tool.execute.{before,after}); the entrypoint starts
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ function bareVerb(tool) {
|
|||||||
return tool;
|
return tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Named export + loaded from the plugin auto-discovery dir
|
// Named export (opencode's plugin convention) + baked into the plugin
|
||||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 ignores config `plugin:`-array
|
// auto-discovery dir (~/.config/opencode/plugin/) at image build — the simplest
|
||||||
// absolute paths for hook/tool registration (verified live).
|
// registration route (no config `plugin:` path needed).
|
||||||
export const RobocoBudgetFeed = async () => {
|
export const RobocoBudgetFeed = async () => {
|
||||||
return {
|
return {
|
||||||
"tool.execute.before": async (input) => {
|
"tool.execute.before": async (input) => {
|
||||||
|
|||||||
+45
-23
@@ -1,29 +1,28 @@
|
|||||||
// opencode plugin — the Intake interviewer's propose_draft tool, on Grok.
|
// opencode plugin — the Intake interviewer's propose_draft tool, on Grok.
|
||||||
//
|
//
|
||||||
// Parity with the Claude Intake's SDK tool (roboco.agent_sdk.intake_driver
|
// The model calls propose_draft once the task spec is ready; this delivers the
|
||||||
// .build_intake_options): the model calls propose_draft once the task spec is
|
// draft to the panel's reviewable draft card.
|
||||||
// ready, and the driver (OpencodeServeSession.normalize_opencode_message ->
|
|
||||||
// _is_propose_draft -> _draft_from_tool_input) turns that tool call into the
|
|
||||||
// `draft` chunk the panel renders as the reviewable draft card.
|
|
||||||
//
|
//
|
||||||
// Without this, propose_draft is a tool the prompter prompt tells the model to
|
// WHY IT POSTS DIRECTLY (not via the driver): opencode's synchronous serve reply
|
||||||
// call but that does not exist on Grok, so no draft card ever appears and the
|
// (POST /session/:id/message) returns only the final assistant text + step
|
||||||
// human can't launch a task from a Grok intake chat. The execute() only ACKs —
|
// markers — NOT the tool-CALL parts. So OpencodeServeSession cannot intercept
|
||||||
// the payload that matters is the tool-CALL input, which the driver intercepts.
|
// this call to emit a `draft` chunk (verified live: a propose_draft call comes
|
||||||
|
// back as parts=[step-start, text, step-finish], no tool part). Instead the tool
|
||||||
|
// POSTs the draft straight to the prompter-live relay — the same
|
||||||
|
// /api/prompter/live/{session}/events endpoint the driver's relay sink uses — so
|
||||||
|
// the panel renders the card regardless. (The Claude intake path differs: the
|
||||||
|
// Claude SDK DOES expose the tool-use block, so its driver intercepts it.)
|
||||||
//
|
//
|
||||||
// Loaded ONLY into the roboco-agent-grok-prompter image via
|
// Loaded from the plugin auto-discovery dir (~/.config/opencode/plugin/), baked
|
||||||
// ROBOCO_OPENCODE_EXTRA_PLUGINS (the one-shot delivery roles never draft).
|
// into the grok-prompter image only (the one-shot delivery roles never draft).
|
||||||
//
|
// The container provides ROBOCO_API_URL + ROBOCO_PROMPTER_SESSION_ID.
|
||||||
// UNVERIFIED-LIVE: opencode's exact tool-call Part shape in the synchronous
|
|
||||||
// serve reply — confirm a Grok intake spec yields a draft chunk -> panel card
|
|
||||||
// on the NAS before relying on Grok intake.
|
|
||||||
|
|
||||||
import { tool } from "@opencode-ai/plugin";
|
import { tool } from "@opencode-ai/plugin";
|
||||||
|
|
||||||
// Named export + loaded from the plugin auto-discovery dir
|
const API_BASE = (
|
||||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 only registers Hooks.tool from
|
process.env.ROBOCO_API_URL || "http://roboco-orchestrator:8000"
|
||||||
// directory auto-discovery, not a config `plugin:`-array absolute path
|
).replace(/\/+$/, "");
|
||||||
// (verified live).
|
|
||||||
export const RobocoIntakeTools = async () => ({
|
export const RobocoIntakeTools = async () => ({
|
||||||
tool: {
|
tool: {
|
||||||
propose_draft: tool({
|
propose_draft: tool({
|
||||||
@@ -38,10 +37,33 @@ export const RobocoIntakeTools = async () => ({
|
|||||||
.record(tool.schema.string(), tool.schema.any())
|
.record(tool.schema.string(), tool.schema.any())
|
||||||
.describe("The task draft object"),
|
.describe("The task draft object"),
|
||||||
},
|
},
|
||||||
async execute() {
|
async execute(args) {
|
||||||
// The driver intercepts the tool CALL and emits the draft chunk; this
|
const session = process.env.ROBOCO_PROMPTER_SESSION_ID || "";
|
||||||
// handler only acknowledges so the model knows the draft landed.
|
if (!session) {
|
||||||
return "Draft submitted — the human can review it.";
|
return "No live session id (ROBOCO_PROMPTER_SESSION_ID) — cannot surface the draft.";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${API_BASE}/api/prompter/live/${encodeURIComponent(session)}/events`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
kind: "draft",
|
||||||
|
text: "",
|
||||||
|
tool: "propose_draft",
|
||||||
|
data: args.draft || {},
|
||||||
|
}),
|
||||||
|
signal: AbortSignal.timeout(15000),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
return `Draft relay returned HTTP ${res.status}; the human may not see the card.`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return "Could not submit the draft to the panel: " + String(e);
|
||||||
|
}
|
||||||
|
return "Draft submitted — the human can review it in the panel.";
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -164,10 +164,10 @@ function denyBash(command) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Named export + loaded from opencode's plugin auto-discovery dir
|
// Named export (opencode's plugin convention) + loaded from opencode's plugin
|
||||||
// (~/.config/opencode/plugin/). opencode 1.17.8 does NOT register a plugin's
|
// auto-discovery dir (~/.config/opencode/plugin/), where it's baked at image
|
||||||
// hooks/tools when it's listed by absolute path in the config `plugin:` array —
|
// build — the simplest registration route (no config `plugin:` path needed).
|
||||||
// only directory auto-discovery works (verified live against grok-build-0.1).
|
// Hook firing verified live against grok-build-0.1.
|
||||||
export const RobocoSecretScrub = async () => {
|
export const RobocoSecretScrub = async () => {
|
||||||
return {
|
return {
|
||||||
"tool.execute.before": async (input, output) => {
|
"tool.execute.before": async (input, output) => {
|
||||||
|
|||||||
@@ -66,11 +66,10 @@ async function callBackend(method, path, body) {
|
|||||||
|
|
||||||
const asText = (data) => JSON.stringify(data);
|
const asText = (data) => JSON.stringify(data);
|
||||||
|
|
||||||
// Named export + loaded from the plugin auto-discovery dir
|
// Named export (opencode's plugin convention) + baked into the plugin
|
||||||
// (~/.config/opencode/plugin/) — opencode 1.17.8 does NOT register tools from a
|
// auto-discovery dir (~/.config/opencode/plugin/) at image build. Verified live
|
||||||
// config `plugin:`-array absolute path; only directory auto-discovery + a named
|
// against grok-build-0.1: the model called read_company_state + submit_directive
|
||||||
// export registers Hooks.tool (verified live: the model called the tool and the
|
// and the backend received both requests with the X-Agent-Token.
|
||||||
// backend received the request).
|
|
||||||
export const RobocoSecretaryTools = async () => ({
|
export const RobocoSecretaryTools = async () => ({
|
||||||
tool: {
|
tool: {
|
||||||
read_company_state: tool({
|
read_company_state: tool({
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
The Grok analogue of ``intake_main``: the same in-container ``POST /turn``
|
The Grok analogue of ``intake_main``: the same in-container ``POST /turn``
|
||||||
receiver and the same relay sink to ``/api/prompter/live/{id}/events``, but the
|
receiver and the same relay sink to ``/api/prompter/live/{id}/events``, but the
|
||||||
held-open session is an :class:`OpencodeServeSession` (``opencode serve``)
|
held-open session is an :class:`OpencodeServeSession` (``opencode serve``)
|
||||||
instead of a ``ClaudeSDKClient``. ``opencode.json`` (xAI provider + system
|
instead of a ``ClaudeSDKClient``. ``opencode.json`` (model + system prompt) is
|
||||||
prompt) is rendered first. Intake is a human-only interviewer (no gateway verbs);
|
rendered first. Intake is a human-only interviewer (no gateway verbs); its one
|
||||||
its one action tool, ``propose_draft``, is registered by the ``intake-tools.js``
|
action tool, ``propose_draft``, is registered by the ``intake-tools.js`` opencode
|
||||||
opencode plugin (baked into the grok-prompter image, wired via
|
plugin (baked into the grok-prompter image's plugin auto-discovery dir). Because
|
||||||
``ROBOCO_OPENCODE_EXTRA_PLUGINS``) and the driver turns that tool call into the
|
opencode's synchronous serve reply does NOT carry tool-call parts, that plugin
|
||||||
panel's draft card. The ``IntakeDriver`` loop, message source, and relay are
|
POSTs the draft straight to the prompter-live relay (the same
|
||||||
reused unchanged — only the ``SessionFactory`` differs.
|
``/api/prompter/live/{id}/events`` endpoint) so the panel renders the draft card.
|
||||||
|
The ``IntakeDriver`` loop, message source, and relay are reused unchanged — only
|
||||||
|
the ``SessionFactory`` differs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|||||||
@@ -105,11 +105,15 @@ def normalize_opencode_message(message: dict[str, Any]) -> list[StreamChunk]:
|
|||||||
|
|
||||||
Unlike the Claude path (which streams text deltas live and so drops the final
|
Unlike the Claude path (which streams text deltas live and so drops the final
|
||||||
TextBlock to avoid double-render), the synchronous opencode reply carries the
|
TextBlock to avoid double-render), the synchronous opencode reply carries the
|
||||||
text only here, so text parts ARE emitted. A ``propose_draft`` tool part — or
|
text only here, so text parts ARE emitted. A turn-level ``info.error`` is
|
||||||
a fenced ```roboco-draft``` block in the assembled text — becomes a ``draft``
|
surfaced as an ``error`` chunk so a failed turn is never silently blank.
|
||||||
chunk, matching the Claude intake's two draft paths. A turn-level
|
|
||||||
``info.error`` is surfaced as an ``error`` chunk so a failed turn is never
|
Draft note: opencode's synchronous serve reply does NOT include tool-call
|
||||||
silently blank.
|
parts, so the intake draft is delivered by the ``intake-tools.js``
|
||||||
|
propose_draft tool POSTing to the prompter-live relay directly (not from
|
||||||
|
here). The ``propose_draft`` tool-part and fenced ```roboco-draft``` handling
|
||||||
|
below is a tolerant fallback for any opencode version that DOES surface the
|
||||||
|
tool call in parts; it is normally a no-op on the serve path.
|
||||||
"""
|
"""
|
||||||
parts = message.get("parts") or []
|
parts = message.get("parts") or []
|
||||||
chunks: list[StreamChunk] = []
|
chunks: list[StreamChunk] = []
|
||||||
|
|||||||
@@ -70,13 +70,16 @@ _SYSTEM_PROMPT_IN_CONTAINER = "/app/system-prompt.md"
|
|||||||
_OPENCODE_DATA_DIR_IN_CONTAINER = "/home/agent/.local/share/opencode"
|
_OPENCODE_DATA_DIR_IN_CONTAINER = "/home/agent/.local/share/opencode"
|
||||||
|
|
||||||
# Reasoning effort by role. grok-build-0.1 reasons heavily by default, and
|
# Reasoning effort by role. grok-build-0.1 reasons heavily by default, and
|
||||||
# reasoning bills at the output rate — it dominates cost (a live "say ok" call
|
# reasoning bills at the output rate, so it dominates cost. Code-quality roles
|
||||||
# emitted ~300 reasoning tokens). Code-quality roles (developer, qa, pr_reviewer)
|
# (developer, qa, pr_reviewer) keep full reasoning; coordination / docs / board
|
||||||
# keep full reasoning; coordination / docs / board roles run "minimal" (a live
|
# roles request "minimal". opencode receives this via its `--variant` flag (and
|
||||||
# test cut reasoning ~54% with no quality cost for that work). opencode applies
|
# the serve message `variant` field). NOTE: whether opencode actually applies a
|
||||||
# this via its `--variant` flag. Operators can force one effort for ALL grok
|
# named reasoning variant to grok-build-0.1 without a provider-defined `variants`
|
||||||
# agents with the ROBOCO_GROK_REASONING_EFFORT env on the orchestrator
|
# block is UNVERIFIED — passing the flag does not error, but the reasoning-cost
|
||||||
# (value "minimal" | "high" | "max", or "default"/"full" to use full reasoning).
|
# reduction must be measured on the NAS; treat the saving as best-effort, not
|
||||||
|
# guaranteed. Operators can force one effort for ALL grok agents with the
|
||||||
|
# ROBOCO_GROK_REASONING_EFFORT env on the orchestrator (value "minimal" | "high"
|
||||||
|
# | "max", or "default"/"full" to use full reasoning).
|
||||||
_MINIMAL_REASONING_ROLES = frozenset(
|
_MINIMAL_REASONING_ROLES = frozenset(
|
||||||
{
|
{
|
||||||
"cell_pm",
|
"cell_pm",
|
||||||
@@ -262,8 +265,7 @@ class GrokProvider(AgentProvider):
|
|||||||
[
|
[
|
||||||
# opencode's BUILT-IN xai provider authenticates from XAI_API_KEY
|
# opencode's BUILT-IN xai provider authenticates from XAI_API_KEY
|
||||||
# and reads XAI_BASE_URL for the endpoint — opencode_config emits
|
# and reads XAI_BASE_URL for the endpoint — opencode_config emits
|
||||||
# no provider block (any provider.xai block breaks plugin-tool
|
# no provider block, so these envs are the only LLM wiring needed.
|
||||||
# registration), so these envs are the only LLM wiring.
|
|
||||||
"-e",
|
"-e",
|
||||||
f"XAI_API_KEY={config.provider_auth_token}",
|
f"XAI_API_KEY={config.provider_auth_token}",
|
||||||
"-e",
|
"-e",
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ sets (``OPENAI_*`` + ``ROBOCO_*``) plus the mounted Claude Code
|
|||||||
as importable Python (not a shell heredoc) makes the translation unit-testable.
|
as importable Python (not a shell heredoc) makes the translation unit-testable.
|
||||||
|
|
||||||
Config shape per opencode docs (https://opencode.ai/docs/config):
|
Config shape per opencode docs (https://opencode.ai/docs/config):
|
||||||
* NO ``provider`` block. opencode's BUILT-IN xai provider drives
|
* NO ``provider`` block — opencode's BUILT-IN xai provider already drives
|
||||||
grok-build-0.1; ANY custom ``provider.xai`` block (even just ``options``)
|
grok-build-0.1 (model resolution + tool-calls verified live), so a custom
|
||||||
breaks plugin-tool registration, and a ``npm`` override additionally breaks
|
block is unnecessary. The key + base URL reach the provider via the
|
||||||
model resolution (ProviderModelNotFoundError) — all verified live on opencode
|
``XAI_API_KEY`` / ``XAI_BASE_URL`` env vars; ``model`` selects ``xai/<model>``.
|
||||||
1.17.8. The key + base URL reach the provider via the ``XAI_API_KEY`` /
|
|
||||||
``XAI_BASE_URL`` env vars; ``model`` selects ``xai/<model>``.
|
|
||||||
* ``mcp.<name>`` — ``{type:"local", command:[...], environment:{...}}``; this
|
* ``mcp.<name>`` — ``{type:"local", command:[...], environment:{...}}``; this
|
||||||
is where RoboCo's gateway servers (roboco-flow / roboco-do / ...) are wired,
|
is where RoboCo's gateway servers (roboco-flow / roboco-do / ...) are wired,
|
||||||
translated from Claude Code's ``mcpServers`` (``command`` + ``args`` + ``env``).
|
translated from Claude Code's ``mcpServers`` (``command`` + ``args`` + ``env``).
|
||||||
@@ -25,12 +23,14 @@ Config shape per opencode docs (https://opencode.ai/docs/config):
|
|||||||
review). This is the primary idle-stream defence (the orchestrator reaper is
|
review). This is the primary idle-stream defence (the orchestrator reaper is
|
||||||
the backstop).
|
the backstop).
|
||||||
|
|
||||||
There is NO ``plugin`` key: opencode 1.17.8 does not register a plugin's
|
There is NO ``plugin`` key: the plugins are baked into opencode's plugin
|
||||||
hooks/tools from a config ``plugin:``-array absolute path — only from the plugin
|
AUTO-DISCOVERY dir (``~/.config/opencode/plugin/``, i.e.
|
||||||
AUTO-DISCOVERY dir (``~/.config/opencode/plugin/``). The plugins are baked there
|
``/home/agent/.config/opencode/plugin/`` in the image), so the generated config
|
||||||
in the images instead (secret-scrub + budget-feed in the base grok image; the
|
doesn't need to reference them by path (secret-scrub + budget-feed in the base
|
||||||
Secretary's directive tools and the Intake's propose_draft in their interactive
|
grok image; the Secretary's directive tools and the Intake's propose_draft in
|
||||||
images).
|
their interactive images). Each uses a NAMED export (opencode's documented
|
||||||
|
plugin convention). The model writes this config to the GLOBAL location (see
|
||||||
|
``main`` — ``~/.config/opencode/opencode.json``), which is what opencode reads.
|
||||||
|
|
||||||
GUARDRAIL PARITY: the bash-guard (PAT-scrub) is ported via ``secret-scrub.js``
|
GUARDRAIL PARITY: the bash-guard (PAT-scrub) is ported via ``secret-scrub.js``
|
||||||
(``tool.execute.before``); the per-session budget / loop / terminal-verb
|
(``tool.execute.before``); the per-session budget / loop / terminal-verb
|
||||||
@@ -57,19 +57,16 @@ from typing import Any
|
|||||||
|
|
||||||
_OPENCODE_SCHEMA = "https://opencode.ai/config.json"
|
_OPENCODE_SCHEMA = "https://opencode.ai/config.json"
|
||||||
_PROVIDER_ID = "xai"
|
_PROVIDER_ID = "xai"
|
||||||
# We do NOT override provider.<id>.npm. opencode's BUILT-IN xai provider already
|
# No provider block at all: opencode's BUILT-IN xai provider already drives
|
||||||
# drives grok-build-0.1 with working tool-calls (verified live); a custom `npm`
|
# grok-build-0.1 with working tool-calls (verified live), so no custom `npm` /
|
||||||
# (e.g. @ai-sdk/openai) is not resolvable from opencode's module path and makes
|
# `models` override is needed. The xAI key is injected via the XAI_API_KEY env
|
||||||
# the model fail to resolve (ProviderModelNotFoundError). The xAI key is injected
|
# var the built-in provider reads (set by GrokProvider / the orchestrator).
|
||||||
# via the XAI_API_KEY env var the built-in provider reads (set by GrokProvider /
|
|
||||||
# the orchestrator) — provider.options.apiKey alone does NOT authenticate it.
|
|
||||||
#
|
#
|
||||||
# Plugins (secret-scrub / budget-feed / the per-role tool plugins) are NOT listed
|
# Plugins (secret-scrub / budget-feed / the per-role tool plugins) are baked into
|
||||||
# in the config `plugin:` array — opencode 1.17.8 does not register a plugin's
|
|
||||||
# hooks/tools when it is referenced by absolute path there. They are baked into
|
|
||||||
# the plugin AUTO-DISCOVERY dir (~/.config/opencode/plugin/, i.e.
|
# the plugin AUTO-DISCOVERY dir (~/.config/opencode/plugin/, i.e.
|
||||||
# /home/agent/.config/opencode/plugin/ in the image) instead, which registers
|
# /home/agent/.config/opencode/plugin/ in the image) rather than referenced by a
|
||||||
# both tools and hooks (verified live against grok-build-0.1).
|
# config `plugin:` path — the dir is the simplest registration route and keeps
|
||||||
|
# the generated config path-free (registration verified live against grok-build-0.1).
|
||||||
|
|
||||||
|
|
||||||
# opencode's built-in subagent-spawning tool. Hard-disabled in the generated
|
# opencode's built-in subagent-spawning tool. Hard-disabled in the generated
|
||||||
@@ -77,8 +74,8 @@ _PROVIDER_ID = "xai"
|
|||||||
# subagents, and one that does can wedge the parent run on an idle stream. This
|
# subagents, and one that does can wedge the parent run on an idle stream. This
|
||||||
# is the primary defence against the idle-stream hang; the orchestrator's
|
# is the primary defence against the idle-stream hang; the orchestrator's
|
||||||
# reaper watchdog (_maybe_kill_wedged_grok) is the backstop. (Per-provider
|
# reaper watchdog (_maybe_kill_wedged_grok) is the backstop. (Per-provider
|
||||||
# request/stream timeouts can't be set without a custom provider.npm, which
|
# request/stream timeouts would need a custom provider block, which we don't
|
||||||
# breaks model resolution — see the module docstring — so they are not used.)
|
# emit; the reaper + disabled subagents cover the idle-stream risk instead.)
|
||||||
_SUBAGENT_TOOL = "task"
|
_SUBAGENT_TOOL = "task"
|
||||||
|
|
||||||
|
|
||||||
@@ -134,13 +131,11 @@ def build_opencode_config(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Build the ``opencode.json`` dict for a Grok agent.
|
"""Build the ``opencode.json`` dict for a Grok agent.
|
||||||
|
|
||||||
Emits NO ``provider`` block: opencode's BUILT-IN xai provider drives
|
Emits NO ``provider`` block — opencode's BUILT-IN xai provider drives
|
||||||
grok-build-0.1, and ANY custom ``provider.xai`` block breaks plugin-tool
|
grok-build-0.1 (verified live), so a custom block is unnecessary; the key +
|
||||||
registration AND (without ``XAI_API_KEY``) model resolution — all verified
|
base URL are injected via the ``XAI_API_KEY`` / ``XAI_BASE_URL`` env vars
|
||||||
live on opencode 1.17.8. The key + base URL are injected via the
|
(set by GrokProvider / the orchestrator). No ``plugin`` array either —
|
||||||
``XAI_API_KEY`` / ``XAI_BASE_URL`` env vars (set by GrokProvider / the
|
plugins live in the auto-discovery dir baked into the images.
|
||||||
orchestrator). No ``plugin`` array either — plugins live in the
|
|
||||||
auto-discovery dir baked into the images.
|
|
||||||
"""
|
"""
|
||||||
guards = guards or OpencodeGuards()
|
guards = guards or OpencodeGuards()
|
||||||
config: dict[str, Any] = {
|
config: dict[str, Any] = {
|
||||||
|
|||||||
@@ -3443,8 +3443,8 @@ class AgentOrchestrator:
|
|||||||
cmd.extend(
|
cmd.extend(
|
||||||
[
|
[
|
||||||
# Built-in xai provider authenticates from XAI_API_KEY and
|
# Built-in xai provider authenticates from XAI_API_KEY and
|
||||||
# reads XAI_BASE_URL; opencode_config emits no provider block
|
# reads XAI_BASE_URL; opencode_config emits no provider block,
|
||||||
# (any provider.xai block breaks plugin-tool registration).
|
# so these envs are the only LLM wiring needed.
|
||||||
"-e",
|
"-e",
|
||||||
f"XAI_API_KEY={auth_token or ''}",
|
f"XAI_API_KEY={auth_token or ''}",
|
||||||
"-e",
|
"-e",
|
||||||
|
|||||||
Reference in New Issue
Block a user