subconscious-cli 4.1.3 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,20 +6,24 @@
6
6
  # The model catalog (needed to suppress the "model metadata not found" warning)
7
7
  # is written to a temp file that is cleaned up on exit.
8
8
  #
9
- # Subagents are OFF by default. Current Codex (>=0.144) wraps subagent tools in a
10
- # `type: "namespace"` wire format that non-OpenAI providers can't resolve, causing
11
- # "unsupported call: spawn_agent" (upstream #32318, #26977). The fix (PR #29602) is
12
- # not yet merged.
9
+ # Subagents are ON by default. The per-model `multi_agent_version` catalog
10
+ # field selects WHICH implementation Codex offers, not whether it offers one:
11
+ # "v2" gives the `collaboration` namespace, and an empty value falls back to
12
+ # the older `multi_agent_v1` namespace rather than turning subagents off.
13
+ # Only `agents.enabled=false` actually disables them, so clearing
14
+ # CODEX_MULTI_AGENT_VERSION does both.
13
15
  #
14
- # To run with subagents, pass --subagents — this runs the pinned legacy
15
- # codex@0.132.0 with multi-agent v1 config (plain tool names) that the model can
16
- # resolve. Requires npx.
16
+ # This needs a gateway that round-trips Codex's namespaced tools. Codex sends
17
+ # them as `type: "namespace"` and resolves an incoming call by namespace plus
18
+ # name, so a gateway that flattens the namespace for the model must restore it
19
+ # on the way back, or every call fails with "unsupported call: spawn_agent".
17
20
  #
18
21
  # Usage:
19
22
  # ./run.sh # uses GATEWAY_URL/API_KEY from ../.env
20
23
  # ./run.sh --context-window 5000000 -- --resume
21
- # ./run.sh --subagents # run codex@0.132.0 with subagents enabled
22
- # ./run.sh --subagents -- --resume # subagents + passthrough args
24
+ # ./run.sh --stream-idle-timeout 1800000 # allow a 30min silent think
25
+ # ./run.sh --max-subagents 12 # raise the concurrent-subagent ceiling
26
+ # ./run.sh --subagent-effort low # cheaper subagents than the parent
23
27
  # ./run.sh --external-tools # include Codex apps/plugins (may exceed gateway tool limits)
24
28
  #
25
29
  # Config: copy ../env.example to ../.env and edit. .env is gitignored.
@@ -41,16 +45,31 @@ if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
41
45
  GATEWAY_URL="${GATEWAY_URL:-}"
42
46
  API_KEY="${CODEX_API_KEY:-${API_KEY:-}}"
43
47
  MODEL="${MODEL:-subconscious/glm-5.3-marathon}"
44
- MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-4}"
45
- SUBAGENTS=false
48
+ MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-}"
49
+ # Effort for spawned agents, independent of the parent's. Empty inherits the
50
+ # Codex default. See the note above SUBAGENT_ARGS for why max is a poor choice.
51
+ #
52
+ # Low rather than medium: the gateway rounds medium up to high for GLM, and at
53
+ # high a subagent handed an open-ended task keeps deliberating without ever
54
+ # writing the closing `</think>`. The turn then comes back as one long block of
55
+ # prose with no tool call, so the agent reports success having produced no
56
+ # files. Measured against the worker on one broad task: low closed and called a
57
+ # tool in ~1.5k characters, high ran past 76k and was still going.
58
+ CODEX_SUBAGENT_REASONING_EFFORT="${CODEX_SUBAGENT_REASONING_EFFORT-low}"
46
59
  EXTERNAL_TOOLS="${CODEX_EXTERNAL_TOOLS:-false}"
47
60
  CODEX_CONTEXT_WINDOW="${CODEX_CONTEXT_WINDOW:-5000000}"
48
61
  CODEX_MAX_CONTEXT_WINDOW="${CODEX_MAX_CONTEXT_WINDOW:-}"
49
62
  CODEX_AUTO_COMPACT_TOKEN_LIMIT="${CODEX_AUTO_COMPACT_TOKEN_LIMIT:-4500000}"
50
63
  CODEX_REASONING_EFFORT="${CODEX_REASONING_EFFORT:-max}"
51
-
52
- # Codex version that supports the legacy multi-agent v1 config (plain tool names).
53
- SUBAGENT_CODEX_VERSION="0.132.0"
64
+ # How long Codex waits on a silent stream before giving up. The marathon models
65
+ # at max reasoning effort routinely think for longer than five minutes without
66
+ # emitting a token, and a subagent's turn is one uninterrupted think with no
67
+ # visible progress, so the old 300s ceiling cut off work that was still running.
68
+ CODEX_STREAM_IDLE_TIMEOUT_MS="${CODEX_STREAM_IDLE_TIMEOUT_MS:-900000}"
69
+ # Codex gates its subagent tools on this per-model catalog field. Only "v2" is
70
+ # honored by current releases; "v1" registers nothing. Set to empty to opt out.
71
+ # Unset defaults to v2; an explicitly empty value opts out, so use `-` not `:-`.
72
+ CODEX_MULTI_AGENT_VERSION="${CODEX_MULTI_AGENT_VERSION-v2}"
54
73
 
55
74
  # Parse args (only when executed, not sourced)
56
75
  PASSTHRU=()
@@ -73,9 +92,17 @@ if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
73
92
  CODEX_REASONING_EFFORT="${2:-}"
74
93
  shift 2
75
94
  ;;
76
- --subagents)
77
- SUBAGENTS=true
78
- shift
95
+ --stream-idle-timeout)
96
+ CODEX_STREAM_IDLE_TIMEOUT_MS="${2:-}"
97
+ shift 2
98
+ ;;
99
+ --max-subagents)
100
+ MAX_CONCURRENT_SUBAGENTS="${2:-}"
101
+ shift 2
102
+ ;;
103
+ --subagent-effort)
104
+ CODEX_SUBAGENT_REASONING_EFFORT="${2:-}"
105
+ shift 2
79
106
  ;;
80
107
  --external-tools)
81
108
  EXTERNAL_TOOLS=true
@@ -134,7 +161,10 @@ while IFS= read -r model_id; do
134
161
  done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
135
162
 
136
163
  write_model_catalog() {
137
- local catalog_file="$1" model_id vision_fields index=0
164
+ local catalog_file="$1" model_id vision_fields index=0 multi_agent_json="null"
165
+ if [[ -n "$CODEX_MULTI_AGENT_VERSION" ]]; then
166
+ multi_agent_json="\"${CODEX_MULTI_AGENT_VERSION}\""
167
+ fi
138
168
  {
139
169
  printf '{\n "models": [\n'
140
170
  for model_id in "${SUPPORTED_MODELS[@]}"; do
@@ -154,7 +184,13 @@ write_model_catalog() {
154
184
  "max_context_window": ${CODEX_MAX_CONTEXT_WINDOW},
155
185
  "auto_compact_token_limit": ${CODEX_AUTO_COMPACT_TOKEN_LIMIT},
156
186
  "effective_context_window_percent": 95,
157
- "supported_reasoning_levels": [],
187
+ "supported_reasoning_levels": [
188
+ { "effort": "none", "description": "No additional reasoning" },
189
+ { "effort": "low", "description": "Fast responses with lighter reasoning" },
190
+ { "effort": "medium", "description": "Balances speed and reasoning depth" },
191
+ { "effort": "high", "description": "Greater reasoning depth for complex problems" },
192
+ { "effort": "max", "description": "Maximum reasoning depth for the hardest problems" }
193
+ ],
158
194
  "shell_type": "shell_command",
159
195
  "visibility": "list",
160
196
  "supported_in_api": true,
@@ -175,6 +211,8 @@ write_model_catalog() {
175
211
  "apply_patch_tool_type": "freeform",
176
212
  "truncation_policy": { "mode": "tokens", "limit": 10000 },
177
213
  "supports_parallel_tool_calls": true,
214
+ "multi_agent_version": ${multi_agent_json},
215
+ "use_responses_lite": false,
178
216
  "experimental_supported_tools": []${vision_fields}
179
217
  }
180
218
  EOF
@@ -197,6 +235,41 @@ if [[ "$EXTERNAL_TOOLS" != "true" ]]; then
197
235
  )
198
236
  fi
199
237
 
238
+ # Bound the subagent tree when the catalog turns it on. Codex still only spawns
239
+ # a subagent when the user asks for one; these are ceilings, not defaults.
240
+ #
241
+ # Codex documents no nesting-depth limit, so a subagent can spawn its own
242
+ # subagent and the tree can grow deeper than you asked for. Capping concurrency
243
+ # is the only lever available.
244
+ #
245
+ # Subagent reasoning effort is set separately from the parent's on purpose: the
246
+ # marathon models at max effort think for many minutes without emitting a
247
+ # token, and a subagent turn is one uninterrupted think, so inheriting max
248
+ # makes every delegated subtask look like a hang.
249
+ # Applied after argument parsing so --max-subagents can win. Four is a floor
250
+ # rather than a considered limit: Codex's own default is three, and exceeding
251
+ # either one fails the spawn outright rather than queueing it, so a run that
252
+ # wants more should say so instead of collecting retries.
253
+ MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-4}"
254
+
255
+ SUBAGENT_ARGS=()
256
+ if [[ -z "$CODEX_MULTI_AGENT_VERSION" ]]; then
257
+ # Clearing the catalog field alone only downgrades Codex to multi-agent v1,
258
+ # whose tools this gateway has never been exercised against. Turn the
259
+ # feature off outright instead.
260
+ SUBAGENT_ARGS=(-c agents.enabled=false)
261
+ else
262
+ SUBAGENT_ARGS=(
263
+ -c agents.max_concurrent_threads_per_session="${MAX_CONCURRENT_SUBAGENTS}"
264
+ -c agents.interrupt_message=true
265
+ )
266
+ if [[ -n "$CODEX_SUBAGENT_REASONING_EFFORT" ]]; then
267
+ SUBAGENT_ARGS+=(
268
+ -c agents.default_subagent_reasoning_effort="${CODEX_SUBAGENT_REASONING_EFFORT}"
269
+ )
270
+ fi
271
+ fi
272
+
200
273
  if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
201
274
  echo "error: GATEWAY_URL and API_KEY must be set in ../.env" >&2
202
275
  exit 1
@@ -206,6 +279,10 @@ export SUBCONSCIOUS_API_KEY="$API_KEY"
206
279
  export SUBCONSCIOUS_GATEWAY_URL="${GATEWAY_URL%/}"
207
280
 
208
281
  # Merge compaction hooks without replacing ~/.codex/hooks.json or config.toml.
282
+ # `codex_ensure_hooks` returns early when they are already current, so a launch
283
+ # that changes nothing writes nothing: Codex decides what counts as a new hook
284
+ # from what is on disk, and rewriting an identical one reset the trust the user
285
+ # had already granted.
209
286
  HOOK_SRC="${SCRIPT_DIR}/hook.sh"
210
287
  # shellcheck source=hooks-lib.sh
211
288
  source "${SCRIPT_DIR}/hooks-lib.sh"
@@ -220,43 +297,22 @@ write_model_catalog "$CATALOG_FILE"
220
297
 
221
298
  # If sourced, just export env and return.
222
299
  if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
223
- export GATEWAY_URL CATALOG_FILE MAX_CONCURRENT_SUBAGENTS SUBAGENTS
300
+ export GATEWAY_URL CATALOG_FILE MAX_CONCURRENT_SUBAGENTS CODEX_SUBAGENT_REASONING_EFFORT
224
301
  return 0 2>/dev/null || true
225
302
  fi
226
303
 
227
304
  # Ephemeral config via -c flags — nothing is written to ~/.codex/config.toml.
228
- if [[ "$SUBAGENTS" == "true" ]]; then
229
- # Legacy multi-agent v1 config — plain tool names the model can resolve.
230
- echo "Starting codex@${SUBAGENT_CODEX_VERSION} with subagents enabled (max ${MAX_CONCURRENT_SUBAGENTS} threads)..." >&2
231
- exec npx -y "@openai/codex@${SUBAGENT_CODEX_VERSION}" \
232
- -c model="${MODEL}" \
233
- -c model_provider=subconscious \
234
- -c model_catalog_json="${CATALOG_FILE}" \
235
- -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
236
- -c web_search=disabled \
237
- ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
238
- -c features.multi_agent=true \
239
- -c agents.max_threads="${MAX_CONCURRENT_SUBAGENTS}" \
240
- -c agents.max_depth=1 \
241
- -c agents.interrupt_message=true \
242
- -c model_providers.subconscious.name=Subconscious \
243
- -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
244
- -c model_providers.subconscious.wire_api=responses \
245
- -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
246
- -c model_providers.subconscious.stream_idle_timeout_ms=300000 \
247
- ${PASSTHRU[@]+"${PASSTHRU[@]}"}
248
- else
249
- exec codex \
250
- -c model="${MODEL}" \
251
- -c model_provider=subconscious \
252
- -c model_catalog_json="${CATALOG_FILE}" \
253
- -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
254
- -c web_search=disabled \
255
- ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
256
- -c model_providers.subconscious.name=Subconscious \
257
- -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
258
- -c model_providers.subconscious.wire_api=responses \
259
- -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
260
- -c model_providers.subconscious.stream_idle_timeout_ms=300000 \
261
- ${PASSTHRU[@]+"${PASSTHRU[@]}"}
262
- fi
305
+ exec codex \
306
+ -c model="${MODEL}" \
307
+ -c model_provider=subconscious \
308
+ -c model_catalog_json="${CATALOG_FILE}" \
309
+ -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
310
+ -c web_search=disabled \
311
+ ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
312
+ ${SUBAGENT_ARGS[@]+"${SUBAGENT_ARGS[@]}"} \
313
+ -c model_providers.subconscious.name=Subconscious \
314
+ -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
315
+ -c model_providers.subconscious.wire_api=responses \
316
+ -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
317
+ -c model_providers.subconscious.stream_idle_timeout_ms="${CODEX_STREAM_IDLE_TIMEOUT_MS}" \
318
+ ${PASSTHRU[@]+"${PASSTHRU[@]}"}
@@ -55,6 +55,7 @@ set -euo pipefail
55
55
 
56
56
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
57
57
  source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
58
+ source "${SCRIPT_DIR}/provider.sh"
58
59
 
59
60
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
60
61
  SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
@@ -147,19 +148,7 @@ while IFS= read -r model_id; do
147
148
  add_supported_model "$model_id"
148
149
  done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
149
150
 
150
- MODELS_JSON=""
151
- for model_id in "${SUPPORTED_MODELS[@]}"; do
152
- vision_fields=""
153
- if subc_model_supports_vision "$model_id"; then
154
- vision_fields=',"attachment":true,"modalities":{"input":["text","image"],"output":["text"]}'
155
- fi
156
- model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}${vision_fields}}"
157
- if [[ -n "$MODELS_JSON" ]]; then
158
- MODELS_JSON="${MODELS_JSON},${model_json}"
159
- else
160
- MODELS_JSON="$model_json"
161
- fi
162
- done
151
+ subc_opencode_build_models_json
163
152
 
164
153
  OPENCODE_DIR="${HOME}/.opencode"
165
154
  OPENCODE_CONFIG="${OPENCODE_DIR}/opencode.json"
@@ -186,34 +175,21 @@ require_cmds() {
186
175
  write_config() {
187
176
  mkdir -p "$OPENCODE_DIR"
188
177
  local base_url="${GATEWAY_URL%/}/v1"
189
- local provider
190
- provider=$(cat <<EOF
191
- {
192
- "npm": "@ai-sdk/openai-compatible",
193
- "name": "Subconscious Gateway",
194
- "options": {
195
- "baseURL": "${base_url}",
196
- "apiKey": "{env:SUBCONSCIOUS_API_KEY}",
197
- "headers": {
198
- "x-subconscious-client": "opencode"
199
- }
200
- },
201
- "models": {${MODELS_JSON}}
202
- }
203
- EOF
204
- )
178
+ local provider selected_model
179
+ provider="$(subc_opencode_build_provider_json "$base_url")"
180
+ selected_model="${SUBC_OPENCODE_PROVIDER_ID}/${MODEL}"
205
181
  if [[ -f "$OPENCODE_CONFIG" ]]; then
206
182
  local tmp
207
183
  tmp="$(mktemp)"
208
- jq --argjson provider "$provider" --arg model "subconscious/${MODEL}" '
184
+ jq --argjson provider "$provider" --arg model "$selected_model" --arg provider_id "$SUBC_OPENCODE_PROVIDER_ID" '
209
185
  .provider = (.provider // {})
210
- | .provider.subconscious = $provider
186
+ | .provider[$provider_id] = $provider
211
187
  | .model = $model
212
188
  ' "$OPENCODE_CONFIG" >"$tmp"
213
189
  mv "$tmp" "$OPENCODE_CONFIG"
214
190
  else
215
- jq -n --argjson provider "$provider" --arg model "subconscious/${MODEL}" \
216
- '{ "$schema": "https://opencode.ai/config.json", provider: {subconscious: $provider}, model: $model }' \
191
+ jq -n --argjson provider "$provider" --arg model "$selected_model" --arg provider_id "$SUBC_OPENCODE_PROVIDER_ID" \
192
+ '{ "$schema": "https://opencode.ai/config.json", provider: {($provider_id): $provider}, model: $model }' \
217
193
  >"$OPENCODE_CONFIG"
218
194
  fi
219
195
  local env_file="${OPENCODE_DIR}/subconscious.env"
@@ -240,7 +216,7 @@ uninstall_config() {
240
216
  local tmp
241
217
  tmp="$(mktemp)"
242
218
  jq '
243
- del(.provider.subconscious)
219
+ del(.provider.subconscious, .provider["subconscious-cli"])
244
220
  | if ((.model // "") | tostring | startswith("subconscious/")) then del(.model) else . end
245
221
  ' "$OPENCODE_CONFIG" >"$tmp"
246
222
  mv "$tmp" "$OPENCODE_CONFIG"
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+ # Shared OpenCode provider metadata for subc launch and install paths.
3
+
4
+ # OpenCode groups /models entries by provider.name. Keep this stable and human
5
+ # readable.
6
+ SUBC_OPENCODE_PROVIDER_ID="subconscious"
7
+ SUBC_OPENCODE_PROVIDER_NAME="Subconscious Gateway"
8
+
9
+ subc_opencode_model_display_name() {
10
+ local slug="${1:-}"
11
+ slug="${slug#subconscious/}"
12
+ echo "$slug" | awk -F '[-_]' '
13
+ function format_token(token, lower) {
14
+ lower = tolower(token)
15
+ if (lower ~ /^(gpt|oss|api|gguf|ggml|nomic|vl|it|mlx)$/) return toupper(token)
16
+ if (token ~ /^[0-9]+[bBkKmMgG]$/) return toupper(token)
17
+ if (token ~ /^[qQ][0-9]+$/) return toupper(token)
18
+ if (token ~ /^[0-9]+\.[0-9]+/) return token
19
+ if (token ~ /^[A-Za-z][0-9]+[A-Za-z]$/ || token ~ /^[0-9]+[A-Za-z]$/) return toupper(token)
20
+ return toupper(substr(token, 1, 1)) tolower(substr(token, 2))
21
+ }
22
+ {
23
+ out = ""
24
+ for (i = 1; i <= NF; i++) {
25
+ if ($i == "") continue
26
+ out = out (out == "" ? "" : " ") format_token($i)
27
+ }
28
+ print out
29
+ }
30
+ '
31
+ }
32
+
33
+ subc_opencode_build_whitelist_json() {
34
+ local model_id
35
+ WHITELIST_JSON=""
36
+ for model_id in "${SUPPORTED_MODELS[@]}"; do
37
+ if [[ -n "$WHITELIST_JSON" ]]; then
38
+ WHITELIST_JSON="${WHITELIST_JSON},\"${model_id}\""
39
+ else
40
+ WHITELIST_JSON="\"${model_id}\""
41
+ fi
42
+ done
43
+ WHITELIST_JSON="[${WHITELIST_JSON}]"
44
+ }
45
+
46
+ subc_opencode_build_models_json() {
47
+ local model_id display_name vision_fields model_json
48
+ MODELS_JSON=""
49
+ for model_id in "${SUPPORTED_MODELS[@]}"; do
50
+ display_name="$(subc_opencode_model_display_name "$model_id")"
51
+ vision_fields=""
52
+ if subc_model_supports_vision "$model_id"; then
53
+ vision_fields=',"attachment":true,"modalities":{"input":["text","image"],"output":["text"]}'
54
+ fi
55
+ model_json="\"${model_id}\":{\"name\":\"${display_name}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}${vision_fields}}"
56
+ if [[ -n "$MODELS_JSON" ]]; then
57
+ MODELS_JSON="${MODELS_JSON},${model_json}"
58
+ else
59
+ MODELS_JSON="$model_json"
60
+ fi
61
+ done
62
+ subc_opencode_build_whitelist_json
63
+ }
64
+
65
+ subc_opencode_build_config_json() {
66
+ local base_url="${1:?base url required}"
67
+ local model="${2:?model required}"
68
+ cat <<EOF
69
+ {"\$schema":"https://opencode.ai/config.json","disabled_providers":["subconscious-cli"],"provider":{"${SUBC_OPENCODE_PROVIDER_ID}":{"npm":"@ai-sdk/openai-compatible","name":"${SUBC_OPENCODE_PROVIDER_NAME}","whitelist":${WHITELIST_JSON},"options":{"baseURL":"${base_url}","apiKey":"{env:SUBCONSCIOUS_API_KEY}","headers":{"x-subconscious-client":"opencode"},"modelsDiscovery":{"enabled":false}},"models":{${MODELS_JSON}}}},"model":"${SUBC_OPENCODE_PROVIDER_ID}/${model}"}
70
+ EOF
71
+ }
72
+
73
+ subc_opencode_build_provider_json() {
74
+ local base_url="${1:?base url required}"
75
+ cat <<EOF
76
+ {
77
+ "npm": "@ai-sdk/openai-compatible",
78
+ "name": "${SUBC_OPENCODE_PROVIDER_NAME}",
79
+ "whitelist": ${WHITELIST_JSON},
80
+ "options": {
81
+ "baseURL": "${base_url}",
82
+ "apiKey": "{env:SUBCONSCIOUS_API_KEY}",
83
+ "headers": {
84
+ "x-subconscious-client": "opencode"
85
+ },
86
+ "modelsDiscovery": {
87
+ "enabled": false
88
+ }
89
+ },
90
+ "models": {${MODELS_JSON}}
91
+ }
92
+ EOF
93
+ }
@@ -19,6 +19,7 @@ set -euo pipefail
19
19
 
20
20
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
21
  source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
22
+ source "${SCRIPT_DIR}/provider.sh"
22
23
 
23
24
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
24
25
  SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
@@ -58,19 +59,7 @@ while IFS= read -r model_id; do
58
59
  add_supported_model "$model_id"
59
60
  done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
60
61
 
61
- MODELS_JSON=""
62
- for model_id in "${SUPPORTED_MODELS[@]}"; do
63
- vision_fields=""
64
- if subc_model_supports_vision "$model_id"; then
65
- vision_fields=',"attachment":true,"modalities":{"input":["text","image"],"output":["text"]}'
66
- fi
67
- model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}${vision_fields}}"
68
- if [[ -n "$MODELS_JSON" ]]; then
69
- MODELS_JSON="${MODELS_JSON},${model_json}"
70
- else
71
- MODELS_JSON="$model_json"
72
- fi
73
- done
62
+ subc_opencode_build_models_json
74
63
 
75
64
  if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
76
65
  echo "error: GATEWAY_URL and API_KEY must be set in ../.env" >&2
@@ -96,15 +85,11 @@ if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
96
85
  fi
97
86
 
98
87
  BASE_URL="${GATEWAY_URL%/}/v1"
99
- RUNTIME_PROVIDER_ID="subconscious-cli"
100
88
 
101
89
  export SUBCONSCIOUS_API_KEY="$API_KEY"
102
90
  # Compaction reporting needs the plugin on disk; this launch path writes nothing.
103
91
  export SUBCONSCIOUS_GATEWAY_URL="${GATEWAY_URL%/}"
104
- export OPENCODE_CONFIG_CONTENT=$(cat <<EOF
105
- {"\$schema":"https://opencode.ai/config.json","disabled_providers":["subconscious"],"provider":{"${RUNTIME_PROVIDER_ID}":{"npm":"@ai-sdk/openai-compatible","name":"Subconscious Gateway","options":{"baseURL":"${BASE_URL}","apiKey":"{env:SUBCONSCIOUS_API_KEY}","headers":{"x-subconscious-client":"opencode"}},"models":{${MODELS_JSON}}}},"model":"${RUNTIME_PROVIDER_ID}/${MODEL}"}
106
- EOF
107
- )
92
+ export OPENCODE_CONFIG_CONTENT="$(subc_opencode_build_config_json "$BASE_URL" "$MODEL")"
108
93
 
109
94
  # If sourced, just export env and return.
110
95
  if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then