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.
- package/bin/agents.js +29 -6
- package/bin/cli.js +13 -3
- package/bin/models.js +51 -9
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/opencode-provider.js +61 -0
- package/bin/profiles.js +25 -0
- package/bin/registry.generated.json +1 -1
- package/bin/runbook/claude-code/run.sh +5 -0
- package/bin/runbook/codex/hooks-lib.sh +32 -2
- package/bin/runbook/codex/install.sh +10 -0
- package/bin/runbook/codex/run.sh +111 -55
- package/bin/runbook/opencode/install.sh +10 -34
- package/bin/runbook/opencode/provider.sh +93 -0
- package/bin/runbook/opencode/run.sh +3 -18
- package/bin/tui.js +247 -51
- package/bin/update-check.js +29 -8
- package/bin/usage.js +51 -19
- package/bin/windows/launch.js +4 -10
- package/package.json +1 -1
package/bin/runbook/codex/run.sh
CHANGED
|
@@ -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
|
|
10
|
-
#
|
|
11
|
-
# "
|
|
12
|
-
#
|
|
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
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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 --
|
|
22
|
-
# ./run.sh --subagents
|
|
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:-
|
|
45
|
-
|
|
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
|
-
#
|
|
53
|
-
|
|
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
|
-
--
|
|
77
|
-
|
|
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
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
|
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 "
|
|
184
|
+
jq --argjson provider "$provider" --arg model "$selected_model" --arg provider_id "$SUBC_OPENCODE_PROVIDER_ID" '
|
|
209
185
|
.provider = (.provider // {})
|
|
210
|
-
| .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 "
|
|
216
|
-
'{ "$schema": "https://opencode.ai/config.json", provider: {
|
|
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
|
-
|
|
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
|
|
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
|