Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b930d8e39 | |||
| 3c0d00b43f | |||
| 360321db53 | |||
| 1a9168d632 | |||
| 03689e3d9a | |||
| 67840629eb |
@@ -180,7 +180,7 @@ jobs:
|
||||
# environment pypi-publish. The action mints a short-lived OIDC
|
||||
# token and exchanges it for a PyPI upload credential — no static
|
||||
# API token in this repo's secrets.
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
||||
with:
|
||||
packages-dir: ${{ runner.temp }}/runtime-build/dist/
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
|
||||
@@ -4,7 +4,6 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
go.moleculesai.app/plugin/gh-identity v0.0.0-20260509010445-788988195fce
|
||||
github.com/alicebob/miniredis/v2 v2.37.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
@@ -19,6 +18,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
go.moleculesai.app/plugin/gh-identity v0.0.0-20260509010445-788988195fce
|
||||
golang.org/x/crypto v0.50.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -4,8 +4,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Molecule-AI/molecule-ai-plugin-gh-identity v0.0.0-20260424033845-4fd5ac7be30f h1:YkLRhUg+9qr9OV9N8dG1Hj0Ml7TThHlRwh5F//oUJVs=
|
||||
github.com/Molecule-AI/molecule-ai-plugin-gh-identity v0.0.0-20260424033845-4fd5ac7be30f/go.mod h1:NqdtlWZDJvpXNJRHnMkPhTKHdA1LZTNH+63TB66JSOU=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68=
|
||||
github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -154,6 +152,8 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.moleculesai.app/plugin/gh-identity v0.0.0-20260509010445-788988195fce h1:ftm0ba0ukLlfqeFes+/jWnXH8XULXmRpMy3fOCZ83/U=
|
||||
go.moleculesai.app/plugin/gh-identity v0.0.0-20260509010445-788988195fce/go.mod h1:0aAqoDle2V7Cywso94MXdv1DH/HEe/0oZmcbqWYMK7g=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
|
||||
@@ -1262,4 +1262,3 @@ func TestExecuteDelegation_CleanProxyResponse_Unchanged(t *testing.T) {
|
||||
t.Errorf("unmet sqlmock expectations: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,13 +717,16 @@ func deriveProviderFromModelSlug(model string) string {
|
||||
func applyRuntimeModelEnv(envVars map[string]string, runtime, model string) {
|
||||
// Resolution order (priority high → low):
|
||||
// 1. payload.Model (caller passed the canvas-picked model id verbatim)
|
||||
// 2. envVars["MODEL"] (workspace_secret persisted by /org/import via
|
||||
// 2. envVars["MOLECULE_MODEL"] (the canonical, unambiguous name)
|
||||
// 3. envVars["MODEL"] (workspace_secret persisted by /org/import via
|
||||
// the persona env file — MODEL=MiniMax-M2.7-highspeed etc.)
|
||||
// 3. envVars["MODEL_PROVIDER"] (legacy: this secret was historically a
|
||||
// *model id* set by canvas Save+Restart's PUT /model; on the
|
||||
// post-2026-05-08 persona-env convention it's a *provider slug*
|
||||
// (e.g. "minimax") which is NOT a valid model id, so this fallback
|
||||
// only fires when MODEL is absent.)
|
||||
// 4. envVars["MODEL_PROVIDER"] (legacy + misleadingly named: it carries
|
||||
// a *model id*, never the provider — that's LLM_PROVIDER. Historically
|
||||
// set by canvas Save+Restart's PUT /model; the post-2026-05-08
|
||||
// persona-env convention sometimes (mis)set it to a provider slug
|
||||
// ("minimax") or a runtime name ("claude-code"), neither a valid
|
||||
// model id — see internal#226. Only fires when the better-named
|
||||
// vars are absent.)
|
||||
//
|
||||
// Pre-fix bug: this function unconditionally OVERWROTE envVars["MODEL"]
|
||||
// with the MODEL_PROVIDER slug (when payload.Model was empty), wiping
|
||||
@@ -736,6 +739,9 @@ func applyRuntimeModelEnv(envVars map[string]string, runtime, model string) {
|
||||
// and the workspace template's adapter routed to providers[0]
|
||||
// (anthropic-oauth) and wedged at SDK initialize. Caught 2026-05-08
|
||||
// during Phase 4 verification of template-claude-code PR #9.
|
||||
if model == "" {
|
||||
model = envVars["MOLECULE_MODEL"]
|
||||
}
|
||||
if model == "" {
|
||||
model = envVars["MODEL"]
|
||||
}
|
||||
@@ -746,16 +752,18 @@ func applyRuntimeModelEnv(envVars map[string]string, runtime, model string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Universal MODEL env var — every adapter that wants to honour the
|
||||
// canvas-picked model (instead of its template's default) reads this.
|
||||
// molecule-runtime's workspace/config.py already falls back to MODEL
|
||||
// for runtime_config.model (#194). Without this line, the user's
|
||||
// canvas selection is silently dropped on every templated provision —
|
||||
// confirmed via crash-loop diagnosis on 2026-05-02 where MiniMax
|
||||
// picks booted with model=sonnet (template default) and demanded
|
||||
// CLAUDE_CODE_OAUTH_TOKEN. Set it FIRST so the per-runtime branches
|
||||
// below can still layer on additional vendor-specific names without
|
||||
// fighting over the canonical one.
|
||||
// Canonical model env vars — molecule-runtime's workspace/config.py
|
||||
// resolves the picked model as MOLECULE_MODEL > MODEL > (legacy)
|
||||
// MODEL_PROVIDER (#280). Export both new names so adapters can read
|
||||
// either; MODEL stays for backwards compat with everything that
|
||||
// already reads os.environ["MODEL"] (the claude-code adapter does,
|
||||
// since #194). Without this, the user's canvas selection is silently
|
||||
// dropped on every templated provision — confirmed via crash-loop
|
||||
// diagnosis on 2026-05-02 where MiniMax picks booted with model=sonnet
|
||||
// (template default) and demanded CLAUDE_CODE_OAUTH_TOKEN. Set these
|
||||
// FIRST so the per-runtime branches below can layer on additional
|
||||
// vendor-specific names without fighting over the canonical one.
|
||||
envVars["MOLECULE_MODEL"] = model
|
||||
envVars["MODEL"] = model
|
||||
|
||||
switch runtime {
|
||||
|
||||
@@ -665,46 +665,62 @@ func TestApplyRuntimeModelEnv_SetsUniversalMODELForAllRuntimes(t *testing.T) {
|
||||
runtime string
|
||||
model string
|
||||
modelProviderEnv string
|
||||
moleculeModelEnv string
|
||||
wantMODEL string
|
||||
wantHermesDefault string // empty string = must be unset
|
||||
}{
|
||||
{
|
||||
name: "claude-code: picked model populates MODEL",
|
||||
name: "claude-code: picked model populates MODEL + MOLECULE_MODEL",
|
||||
runtime: "claude-code",
|
||||
model: "MiniMax-M2",
|
||||
wantMODEL: "MiniMax-M2",
|
||||
},
|
||||
{
|
||||
name: "hermes: picked model populates BOTH MODEL and HERMES_DEFAULT_MODEL",
|
||||
name: "hermes: picked model populates MODEL, MOLECULE_MODEL, HERMES_DEFAULT_MODEL",
|
||||
runtime: "hermes",
|
||||
model: "minimax/MiniMax-M2.7",
|
||||
wantMODEL: "minimax/MiniMax-M2.7",
|
||||
wantHermesDefault: "minimax/MiniMax-M2.7",
|
||||
},
|
||||
{
|
||||
name: "langgraph: picked model populates MODEL (no vendor-specific name)",
|
||||
name: "langgraph: picked model populates MODEL + MOLECULE_MODEL (no vendor-specific name)",
|
||||
runtime: "langgraph",
|
||||
model: "anthropic:claude-opus-4-7",
|
||||
wantMODEL: "anthropic:claude-opus-4-7",
|
||||
},
|
||||
{
|
||||
name: "crewai: picked model populates MODEL (no vendor-specific name)",
|
||||
name: "crewai: picked model populates MODEL + MOLECULE_MODEL (no vendor-specific name)",
|
||||
runtime: "crewai",
|
||||
model: "openai:gpt-4o",
|
||||
wantMODEL: "openai:gpt-4o",
|
||||
},
|
||||
{
|
||||
name: "empty model + empty MODEL_PROVIDER fallback: nothing set",
|
||||
name: "empty model + no env fallback: nothing set",
|
||||
runtime: "claude-code",
|
||||
model: "",
|
||||
},
|
||||
{
|
||||
name: "empty model + MODEL_PROVIDER fallback hits: MODEL set from secret",
|
||||
name: "empty model + MODEL_PROVIDER fallback hits: MODEL/MOLECULE_MODEL set from secret",
|
||||
runtime: "claude-code",
|
||||
model: "",
|
||||
modelProviderEnv: "MiniMax-M2",
|
||||
wantMODEL: "MiniMax-M2",
|
||||
},
|
||||
{
|
||||
name: "empty model + MOLECULE_MODEL env fallback hits (canonical name)",
|
||||
runtime: "claude-code",
|
||||
model: "",
|
||||
moleculeModelEnv: "opus",
|
||||
wantMODEL: "opus",
|
||||
},
|
||||
{
|
||||
name: "MOLECULE_MODEL beats MODEL_PROVIDER when both set (misnomer guard, internal#226)",
|
||||
runtime: "claude-code",
|
||||
model: "",
|
||||
moleculeModelEnv: "opus",
|
||||
modelProviderEnv: "claude-code",
|
||||
wantMODEL: "opus",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -713,11 +729,18 @@ func TestApplyRuntimeModelEnv_SetsUniversalMODELForAllRuntimes(t *testing.T) {
|
||||
if tc.modelProviderEnv != "" {
|
||||
envVars["MODEL_PROVIDER"] = tc.modelProviderEnv
|
||||
}
|
||||
if tc.moleculeModelEnv != "" {
|
||||
envVars["MOLECULE_MODEL"] = tc.moleculeModelEnv
|
||||
}
|
||||
applyRuntimeModelEnv(envVars, tc.runtime, tc.model)
|
||||
|
||||
if got := envVars["MODEL"]; got != tc.wantMODEL {
|
||||
t.Errorf("MODEL = %q, want %q", got, tc.wantMODEL)
|
||||
}
|
||||
// MOLECULE_MODEL (the canonical name) must mirror MODEL exactly.
|
||||
if got := envVars["MOLECULE_MODEL"]; got != tc.wantMODEL {
|
||||
t.Errorf("MOLECULE_MODEL = %q, want %q", got, tc.wantMODEL)
|
||||
}
|
||||
if got := envVars["HERMES_DEFAULT_MODEL"]; got != tc.wantHermesDefault {
|
||||
t.Errorf("HERMES_DEFAULT_MODEL = %q, want %q", got, tc.wantHermesDefault)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user