mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-12 18:11:51 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60fab764e3 | |||
| e38e63234e |
@@ -54,6 +54,46 @@ Implementation:
|
||||
1. Add MCP server config to the container settings (see `src/container-runner.ts` for how MCP servers are mounted)
|
||||
2. Document available tools in `groups/CLAUDE.md`
|
||||
|
||||
### Changing Provider or Model (per agent group)
|
||||
|
||||
Each agent group picks its own provider (`claude` | `opencode` | `mock`) and its own model via `providers.<name>.model` in `groups/<folder>/container.json`. There are two paths depending on whether the operator is at the keyboard or wants the agent to change itself from chat.
|
||||
|
||||
Questions to ask:
|
||||
- Which group? (look up by name or folder)
|
||||
- Switch provider, change model, or both?
|
||||
- New model id (provider-specific — e.g. `claude-sonnet-4-5`, `openrouter/anthropic/claude-sonnet-4`, `opencode/big-pickle`)
|
||||
|
||||
**From Claude Code (operator editing files):**
|
||||
|
||||
1. For provider switch: `sqlite3 data/v2.db "UPDATE agent_groups SET agent_provider = '<name>' WHERE folder = '<folder>'"`.
|
||||
2. For model: edit `groups/<folder>/container.json` and merge into `providers.<provider>`:
|
||||
```jsonc
|
||||
{ "providers": { "claude": { "model": "claude-sonnet-4-5" } } }
|
||||
```
|
||||
3. Bounce the session's container (kill it or wait for idle) so the next wake picks up the new env.
|
||||
|
||||
**From chat (agent reconfigures itself):**
|
||||
|
||||
Tell the agent (or let it propose it): "use the `set_provider_config` tool to switch my model to X." The tool writes a system action that triggers an approval card to an admin; after approval, the host writes `container.json` and kills the container so the next message spawns with the new env. For provider switching, the DB column still needs a direct edit (no self-mod tool for `agent_groups.agent_provider` yet).
|
||||
|
||||
Shape of the per-group config (what the tool merges into):
|
||||
|
||||
```jsonc
|
||||
// groups/<folder>/container.json
|
||||
{
|
||||
"providers": {
|
||||
"claude": { "model": "claude-sonnet-4-5" },
|
||||
"opencode": {
|
||||
"innerProvider": "openrouter",
|
||||
"model": "openrouter/anthropic/claude-sonnet-4",
|
||||
"smallModel": "openrouter/anthropic/claude-haiku-4.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fallback chain: per-group config → host `.env` (`ANTHROPIC_MODEL` / `OPENCODE_*`) → provider default.
|
||||
|
||||
### Changing Assistant Behavior
|
||||
|
||||
Questions to ask:
|
||||
|
||||
@@ -31,6 +31,14 @@ Then ask in plain text (NOT `AskUserQuestion` — these are free-form):
|
||||
2. **Your display name** — human name, used to name the agent group (`dm-with-<normalized>`) and as the welcome-message addressee. Record as `DISPLAY_NAME`.
|
||||
3. **Agent persona name** — the assistant's display name. Default: `DISPLAY_NAME`. Record as `AGENT_NAME`.
|
||||
|
||||
Then use `AskUserQuestion` for the agent backend:
|
||||
|
||||
4. **Provider** — "Which agent provider should this agent use?" with options `claude` (default, Anthropic Agent SDK) and `opencode` (OpenRouter / Anthropic / etc. via OpenCode — assumes `/add-opencode` has been run). Record as `PROVIDER`.
|
||||
|
||||
Then ask in plain text (free-form — model ids are provider-specific strings):
|
||||
|
||||
5. **Model** — "Which model id? (Leave blank for the provider default.)" For claude: values like `claude-sonnet-4-5`, `claude-opus-4-5`. For opencode: values like `openrouter/anthropic/claude-sonnet-4`, `opencode/big-pickle`. Record as `MODEL` (may be empty).
|
||||
|
||||
## 3. Resolve the DM platform id
|
||||
|
||||
This depends on whether the channel supports cold DM via `adapter.openDM`.
|
||||
@@ -77,10 +85,12 @@ npx tsx scripts/init-first-agent.ts \
|
||||
--user-id "${CHANNEL}:${USER_HANDLE}" \
|
||||
--platform-id "${PLATFORM_ID}" \
|
||||
--display-name "${DISPLAY_NAME}" \
|
||||
--agent-name "${AGENT_NAME}"
|
||||
--agent-name "${AGENT_NAME}" \
|
||||
--provider "${PROVIDER}" \
|
||||
${MODEL:+--model "${MODEL}"}
|
||||
```
|
||||
|
||||
Add `--welcome "System instruction: ..."` to override the default welcome prompt.
|
||||
Pass `--provider` even when the user picked the default `claude`, so the DB column is set explicitly rather than left null. Omit `--model` entirely if the user left it blank — the provider will fall back to the host env / SDK default. Add `--welcome "System instruction: ..."` to override the default welcome prompt.
|
||||
|
||||
The script:
|
||||
1. Upserts the `users` row and grants `owner` role if no owner exists.
|
||||
|
||||
@@ -140,4 +140,49 @@ export const requestRebuild: McpToolDefinition = {
|
||||
},
|
||||
};
|
||||
|
||||
export const selfModTools: McpToolDefinition[] = [installPackages, addMcpServer, requestRebuild];
|
||||
export const setProviderConfig: McpToolDefinition = {
|
||||
tool: {
|
||||
name: 'set_provider_config',
|
||||
description:
|
||||
'Update YOUR agent group\'s provider config (model, inner provider, base URL, etc.) under `providers.<provider>` in `container.json`. Merges with existing fields; pass `null` for a field to clear it. Requires admin approval; fire-and-forget. The container restarts after approval so the new config takes effect on the next message.',
|
||||
inputSchema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
provider: {
|
||||
type: 'string',
|
||||
description: 'Provider name — must match a registered provider (e.g. "claude", "opencode").',
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Fields to merge into `providers.<provider>`. For claude: `{ model: "claude-sonnet-4-5" }`. For opencode: `{ innerProvider, model, smallModel }`. Pass `null` for a field to clear it.',
|
||||
},
|
||||
reason: { type: 'string', description: 'Why this change is needed' },
|
||||
},
|
||||
required: ['provider', 'config'],
|
||||
},
|
||||
},
|
||||
async handler(args) {
|
||||
const provider = (args.provider as string | undefined)?.toLowerCase();
|
||||
const config = args.config as Record<string, unknown> | undefined;
|
||||
if (!provider) return err('provider is required');
|
||||
if (!config || typeof config !== 'object') return err('config must be an object');
|
||||
|
||||
const requestId = generateId();
|
||||
writeMessageOut({
|
||||
id: requestId,
|
||||
kind: 'system',
|
||||
content: JSON.stringify({
|
||||
action: 'set_provider_config',
|
||||
provider,
|
||||
config,
|
||||
reason: (args.reason as string) || '',
|
||||
}),
|
||||
});
|
||||
|
||||
log(`set_provider_config: ${requestId} → ${provider} ${JSON.stringify(config)}`);
|
||||
return ok(`Provider config change submitted. You will be notified when admin approves or rejects.`);
|
||||
},
|
||||
};
|
||||
|
||||
export const selfModTools: McpToolDefinition[] = [installPackages, addMcpServer, requestRebuild, setProviderConfig];
|
||||
|
||||
@@ -16,14 +16,22 @@
|
||||
* --platform-id discord:@me:1491573333382523708 \
|
||||
* --display-name "Gavriel" \
|
||||
* [--agent-name "Andy"] \
|
||||
* [--welcome "System instruction: ..."]
|
||||
* [--welcome "System instruction: ..."] \
|
||||
* [--provider claude|opencode|mock] \
|
||||
* [--model <provider-specific-id>]
|
||||
*
|
||||
* For direct-addressable channels (telegram, whatsapp, etc.), --platform-id
|
||||
* is typically the same as the handle in --user-id, with the channel prefix.
|
||||
*
|
||||
* --provider sets `agent_groups.agent_provider`; --model seeds
|
||||
* `providers.<provider>.model` in the per-group `container.json`. Both are
|
||||
* only applied when creating a new group — pre-existing groups keep their
|
||||
* provider/model settings.
|
||||
*/
|
||||
import path from 'path';
|
||||
|
||||
import { DATA_DIR } from '../src/config.js';
|
||||
import { updateContainerConfig } from '../src/container-config.js';
|
||||
import { createAgentGroup, getAgentGroupByFolder } from '../src/db/agent-groups.js';
|
||||
import { normalizeName } from '../src/db/agent-destinations.js';
|
||||
import { initDb } from '../src/db/connection.js';
|
||||
@@ -47,6 +55,8 @@ interface Args {
|
||||
displayName: string;
|
||||
agentName: string;
|
||||
welcome: string;
|
||||
provider: string | null;
|
||||
model: string | null;
|
||||
}
|
||||
|
||||
const DEFAULT_WELCOME =
|
||||
@@ -82,6 +92,14 @@ function parseArgs(argv: string[]): Args {
|
||||
out.welcome = val;
|
||||
i++;
|
||||
break;
|
||||
case '--provider':
|
||||
out.provider = (val ?? '').toLowerCase() || null;
|
||||
i++;
|
||||
break;
|
||||
case '--model':
|
||||
out.model = val || null;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +118,8 @@ function parseArgs(argv: string[]): Args {
|
||||
displayName: out.displayName!,
|
||||
agentName: out.agentName?.trim() || out.displayName!,
|
||||
welcome: out.welcome?.trim() || DEFAULT_WELCOME,
|
||||
provider: out.provider ?? null,
|
||||
model: out.model ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -146,19 +166,21 @@ async function main(): Promise<void> {
|
||||
|
||||
// 2. Agent group + filesystem
|
||||
const folder = `dm-with-${normalizeName(args.displayName)}`;
|
||||
let ag: AgentGroup | undefined = getAgentGroupByFolder(folder);
|
||||
if (!ag) {
|
||||
const existingAg = getAgentGroupByFolder(folder);
|
||||
let ag: AgentGroup;
|
||||
if (!existingAg) {
|
||||
const agId = generateId('ag');
|
||||
createAgentGroup({
|
||||
id: agId,
|
||||
name: args.agentName,
|
||||
folder,
|
||||
agent_provider: null,
|
||||
agent_provider: args.provider,
|
||||
created_at: now,
|
||||
});
|
||||
ag = getAgentGroupByFolder(folder)!;
|
||||
console.log(`Created agent group: ${ag.id} (${folder})`);
|
||||
console.log(`Created agent group: ${ag.id} (${folder})${args.provider ? ` [provider: ${args.provider}]` : ''}`);
|
||||
} else {
|
||||
ag = existingAg;
|
||||
console.log(`Reusing agent group: ${ag.id} (${folder})`);
|
||||
}
|
||||
initGroupFilesystem(ag, {
|
||||
@@ -168,6 +190,17 @@ async function main(): Promise<void> {
|
||||
'When you receive a system welcome prompt, introduce yourself briefly and invite them to chat. Keep replies concise.',
|
||||
});
|
||||
|
||||
// 2b. Seed per-group provider config if a model was specified. Only applied
|
||||
// on fresh group creation so re-running the script doesn't clobber whatever
|
||||
// the operator has since tuned in container.json.
|
||||
if (!existingAg && args.model) {
|
||||
const provider = (args.provider ?? 'claude').toLowerCase();
|
||||
updateContainerConfig(ag.folder, (cfg) => {
|
||||
cfg.providers[provider] = { ...(cfg.providers[provider] ?? {}), model: args.model };
|
||||
});
|
||||
console.log(`Seeded providers.${provider}.model = ${args.model}`);
|
||||
}
|
||||
|
||||
// 3. DM messaging group
|
||||
const platformId = namespacedPlatformId(args.channel, args.platformId);
|
||||
let mg = getMessagingGroupByPlatform(args.channel, platformId);
|
||||
|
||||
@@ -9,8 +9,16 @@
|
||||
* packages: { apt: string[], npm: string[] }
|
||||
* imageTag?: string // set by buildAgentGroupImage on rebuild
|
||||
* additionalMounts?: Array<{hostPath, containerPath, readonly}>
|
||||
* providers?: { [providerName]: { ... } }
|
||||
* }
|
||||
*
|
||||
* `providers.<name>` is the per-group configuration for a specific agent
|
||||
* provider (model, base URL, inner provider id, etc.). Each provider's
|
||||
* host-side registration (`src/providers/<name>.ts`) reads its own block
|
||||
* via `ctx.providerConfig`. Fields within the block are provider-specific
|
||||
* and loosely typed (`Record<string, unknown>`) — providers validate their
|
||||
* own shape at read time.
|
||||
*
|
||||
* All fields are optional — a missing file or a partial file both resolve
|
||||
* to sensible defaults. Writes are atomic-enough (write-then-rename is not
|
||||
* worth the ceremony here since there's only one writer in practice: the
|
||||
@@ -38,6 +46,8 @@ export interface ContainerConfig {
|
||||
packages: { apt: string[]; npm: string[] };
|
||||
imageTag?: string;
|
||||
additionalMounts: AdditionalMountConfig[];
|
||||
/** Per-provider configuration blocks (model, base URL, etc.), keyed by provider name. */
|
||||
providers: Record<string, Record<string, unknown>>;
|
||||
}
|
||||
|
||||
function emptyConfig(): ContainerConfig {
|
||||
@@ -45,6 +55,7 @@ function emptyConfig(): ContainerConfig {
|
||||
mcpServers: {},
|
||||
packages: { apt: [], npm: [] },
|
||||
additionalMounts: [],
|
||||
providers: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +82,7 @@ export function readContainerConfig(folder: string): ContainerConfig {
|
||||
},
|
||||
imageTag: raw.imageTag,
|
||||
additionalMounts: raw.additionalMounts ?? [],
|
||||
providers: raw.providers ?? {},
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`[container-config] failed to parse ${p}: ${String(err)}`);
|
||||
|
||||
@@ -187,13 +187,15 @@ function resolveProviderContribution(
|
||||
): { provider: string; contribution: ProviderContainerContribution } {
|
||||
const provider = (session.agent_provider || agentGroup.agent_provider || 'claude').toLowerCase();
|
||||
const fn = getProviderContainerConfig(provider);
|
||||
const contribution = fn
|
||||
? fn({
|
||||
sessionDir: sessionDir(agentGroup.id, session.id),
|
||||
agentGroupId: agentGroup.id,
|
||||
hostEnv: process.env,
|
||||
})
|
||||
: {};
|
||||
if (!fn) return { provider, contribution: {} };
|
||||
|
||||
const containerConfig = readContainerConfig(agentGroup.folder);
|
||||
const contribution = fn({
|
||||
sessionDir: sessionDir(agentGroup.id, session.id),
|
||||
agentGroupId: agentGroup.id,
|
||||
hostEnv: process.env,
|
||||
providerConfig: containerConfig.providers[provider],
|
||||
});
|
||||
return { provider, contribution };
|
||||
}
|
||||
|
||||
|
||||
+32
-1
@@ -119,7 +119,7 @@ const APPROVAL_OPTIONS: RawOption[] = [
|
||||
async function requestApproval(
|
||||
session: Session,
|
||||
agentName: string,
|
||||
action: 'install_packages' | 'request_rebuild' | 'add_mcp_server',
|
||||
action: 'install_packages' | 'request_rebuild' | 'add_mcp_server' | 'set_provider_config',
|
||||
payload: Record<string, unknown>,
|
||||
title: string,
|
||||
question: string,
|
||||
@@ -865,6 +865,37 @@ async function handleSystemAction(
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set_provider_config': {
|
||||
const agentGroup = getAgentGroup(session.agent_group_id);
|
||||
if (!agentGroup) {
|
||||
notifyAgent(session, 'set_provider_config failed: agent group not found.');
|
||||
break;
|
||||
}
|
||||
const provider = (content.provider as string | undefined)?.toLowerCase();
|
||||
const config = content.config as Record<string, unknown> | undefined;
|
||||
const reason = (content.reason as string) || '';
|
||||
if (!provider) {
|
||||
notifyAgent(session, 'set_provider_config failed: provider is required.');
|
||||
break;
|
||||
}
|
||||
if (!config || typeof config !== 'object') {
|
||||
notifyAgent(session, 'set_provider_config failed: config must be an object.');
|
||||
break;
|
||||
}
|
||||
const summary = Object.entries(config)
|
||||
.map(([k, v]) => `${k}=${v === null ? '(clear)' : JSON.stringify(v)}`)
|
||||
.join(', ');
|
||||
await requestApproval(
|
||||
session,
|
||||
agentGroup.name,
|
||||
'set_provider_config',
|
||||
{ provider, config, reason },
|
||||
'Set Provider Config',
|
||||
`Agent "${agentGroup.name}" wants to update ${provider} config:\n${summary}${reason ? `\nReason: ${reason}` : ''}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
log.warn('Unknown system action', { action });
|
||||
}
|
||||
|
||||
@@ -320,6 +320,41 @@ async function handleApprovalResponse(
|
||||
killContainer(session.id, 'mcp server added');
|
||||
notify(`MCP server "${payload.name}" added. Your container will restart with it on the next message.`);
|
||||
log.info('MCP server add approved', { approvalId: approval.approval_id, userId });
|
||||
} else if (approval.action === 'set_provider_config') {
|
||||
const agentGroup = getAgentGroup(session.agent_group_id);
|
||||
if (!agentGroup) {
|
||||
notify('set_provider_config approved but agent group missing.');
|
||||
return;
|
||||
}
|
||||
const provider = payload.provider as string;
|
||||
const config = payload.config as Record<string, unknown>;
|
||||
|
||||
updateContainerConfig(agentGroup.folder, (cfg) => {
|
||||
const merged: Record<string, unknown> = { ...(cfg.providers[provider] ?? {}) };
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
// null/undefined clears the field — anything else is a set.
|
||||
if (value === null || value === undefined) delete merged[key];
|
||||
else merged[key] = value;
|
||||
}
|
||||
if (Object.keys(merged).length === 0) {
|
||||
delete cfg.providers[provider];
|
||||
} else {
|
||||
cfg.providers[provider] = merged;
|
||||
}
|
||||
});
|
||||
|
||||
// Kill the container so the next wake picks up the new env from
|
||||
// resolveProviderContribution.
|
||||
killContainer(session.id, 'provider config updated');
|
||||
notify(
|
||||
`Provider config for "${provider}" updated. Your container will restart with the new settings on the next message.`,
|
||||
);
|
||||
log.info('set_provider_config approved', {
|
||||
approvalId: approval.approval_id,
|
||||
userId,
|
||||
provider,
|
||||
fields: Object.keys(config),
|
||||
});
|
||||
}
|
||||
|
||||
deletePendingApproval(approval.approval_id);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Host-side container config for the `claude` provider.
|
||||
*
|
||||
* Claude doesn't need any extra mounts — its session data lives under the
|
||||
* per-group `.claude-shared` mount (set up in `container-runner.ts::buildMounts`
|
||||
* for all providers). This file exists purely to emit `ANTHROPIC_MODEL` from
|
||||
* the per-group config so different agent groups can run different models.
|
||||
*
|
||||
* Per-group config (from `groups/<folder>/container.json::providers.claude`):
|
||||
*
|
||||
* {
|
||||
* "providers": {
|
||||
* "claude": {
|
||||
* "model": "claude-sonnet-4-5" // → ANTHROPIC_MODEL in the container
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Fallback chain: `providers.claude.model` → host `ANTHROPIC_MODEL` → Claude
|
||||
* Code SDK's built-in default (which tracks the current flagship Sonnet).
|
||||
*/
|
||||
import { registerProviderContainerConfig } from './provider-container-registry.js';
|
||||
|
||||
function pickString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
registerProviderContainerConfig('claude', (ctx) => {
|
||||
const cfg = ctx.providerConfig ?? {};
|
||||
const env: Record<string, string> = {};
|
||||
|
||||
const model = pickString(cfg.model) ?? ctx.hostEnv.ANTHROPIC_MODEL;
|
||||
if (model) env.ANTHROPIC_MODEL = model;
|
||||
|
||||
return { env };
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
// Host-side provider container-config barrel.
|
||||
// Providers that need host-side container setup (extra mounts, env passthrough,
|
||||
// per-session directories) self-register on import. Providers with no host
|
||||
// needs (claude, mock) don't appear here.
|
||||
// per-session directories, per-group model selection) self-register on import.
|
||||
//
|
||||
// Skills add a new provider by appending one import line below.
|
||||
|
||||
import './claude.js';
|
||||
import './opencode.js';
|
||||
|
||||
@@ -7,6 +7,19 @@
|
||||
* (read on the host, injected into the container). NO_PROXY / no_proxy are
|
||||
* merged with host values so the in-container OpenCode client can talk to
|
||||
* 127.0.0.1 even when HTTPS_PROXY is set by OneCLI.
|
||||
*
|
||||
* Per-group config (from `groups/<folder>/container.json::providers.opencode`)
|
||||
* takes precedence over the host `.env`:
|
||||
*
|
||||
* {
|
||||
* "providers": {
|
||||
* "opencode": {
|
||||
* "innerProvider": "openrouter", // OPENCODE_PROVIDER
|
||||
* "model": "openrouter/anthropic/claude-sonnet-4", // OPENCODE_MODEL
|
||||
* "smallModel": "openrouter/anthropic/claude-haiku-4.5" // OPENCODE_SMALL_MODEL
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
@@ -28,19 +41,30 @@ function mergeNoProxy(current: string | undefined, additions: string): string {
|
||||
return [...parts].join(',');
|
||||
}
|
||||
|
||||
function pickString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
registerProviderContainerConfig('opencode', (ctx) => {
|
||||
const opencodeDir = path.join(ctx.sessionDir, 'opencode-xdg');
|
||||
fs.mkdirSync(opencodeDir, { recursive: true });
|
||||
|
||||
const cfg = ctx.providerConfig ?? {};
|
||||
|
||||
const env: Record<string, string> = {
|
||||
XDG_DATA_HOME: '/opencode-xdg',
|
||||
NO_PROXY: mergeNoProxy(ctx.hostEnv.NO_PROXY, '127.0.0.1,localhost'),
|
||||
no_proxy: mergeNoProxy(ctx.hostEnv.no_proxy, '127.0.0.1,localhost'),
|
||||
};
|
||||
for (const key of ['OPENCODE_PROVIDER', 'OPENCODE_MODEL', 'OPENCODE_SMALL_MODEL'] as const) {
|
||||
const value = ctx.hostEnv[key];
|
||||
if (value) env[key] = value;
|
||||
}
|
||||
|
||||
// Per-group config wins, host env is the fallback.
|
||||
const innerProvider = pickString(cfg.innerProvider) ?? ctx.hostEnv.OPENCODE_PROVIDER;
|
||||
const model = pickString(cfg.model) ?? ctx.hostEnv.OPENCODE_MODEL;
|
||||
const smallModel = pickString(cfg.smallModel) ?? ctx.hostEnv.OPENCODE_SMALL_MODEL;
|
||||
|
||||
if (innerProvider) env.OPENCODE_PROVIDER = innerProvider;
|
||||
if (model) env.OPENCODE_MODEL = model;
|
||||
if (smallModel) env.OPENCODE_SMALL_MODEL = smallModel;
|
||||
|
||||
return {
|
||||
mounts: [{ hostPath: opencodeDir, containerPath: '/opencode-xdg', readonly: false }],
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface ProviderContainerContext {
|
||||
agentGroupId: string;
|
||||
/** `process.env` at spawn time — pull passthrough values from here. */
|
||||
hostEnv: NodeJS.ProcessEnv;
|
||||
/**
|
||||
* Per-group config block for this provider, read from
|
||||
* `groups/<folder>/container.json` under `providers.<name>`. Shape is
|
||||
* provider-specific; providers should treat individual fields as optional
|
||||
* and fall back to `hostEnv` or sensible defaults.
|
||||
*/
|
||||
providerConfig?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ProviderContainerContribution {
|
||||
|
||||
Reference in New Issue
Block a user