@commonlyai/cli 0.1.93 → 0.1.95
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/package.json +1 -1
- package/src/lib/adapters/opencode.js +84 -15
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode adapter — wraps `opencode run --format json` as a Commonly seat.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* under XDG_DATA_HOME. Trusted seats use the
|
|
6
|
-
* public seats get per-identity state without
|
|
7
|
-
* environment limited to safe launch/locale
|
|
4
|
+
* Smoke-tested version is recorded in TESTED_OPENCODE_VERSION below.
|
|
5
|
+
* OpenCode stores sessions under XDG_DATA_HOME. Trusted seats use the
|
|
6
|
+
* operator's data home directly; public seats get per-identity state without
|
|
7
|
+
* operator auth.json and a child environment limited to safe launch/locale
|
|
8
|
+
* variables. Public provider keys
|
|
8
9
|
* stay in the host adapter and reach the configured provider through a
|
|
9
10
|
* per-spawn loopback proxy; OpenCode receives only that proxy's bearer.
|
|
10
11
|
* This protects the key, not provider spend: the model can use its seat's key
|
|
@@ -56,6 +57,10 @@ import { resolvePublicSandboxMode } from '../sandbox/mode.js';
|
|
|
56
57
|
|
|
57
58
|
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
58
59
|
const TERMINATION_GRACE_MS = 5000;
|
|
60
|
+
// Bump only with a passing real sandbox-exec smoke against the new version.
|
|
61
|
+
const TESTED_OPENCODE_VERSION = '1.18.35';
|
|
62
|
+
const ALLOW_UNTESTED_OPENCODE_VERSION_ENV = 'COMMONLY_OPENCODE_ALLOW_UNTESTED_VERSION';
|
|
63
|
+
const VERSION_CHECK_TIMEOUT_MS = 5000;
|
|
59
64
|
const OPENCODE_CONFIG_GITIGNORE = [
|
|
60
65
|
'node_modules',
|
|
61
66
|
'package.json',
|
|
@@ -713,6 +718,47 @@ const spawnBinaryPath = (env) => {
|
|
|
713
718
|
return 'opencode';
|
|
714
719
|
};
|
|
715
720
|
|
|
721
|
+
const parseOpenCodeVersion = (output) => {
|
|
722
|
+
const match = String(output || '').trim().match(
|
|
723
|
+
/^(?:opencode\s+)?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/i,
|
|
724
|
+
);
|
|
725
|
+
return match?.[1] || null;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const readOpenCodeVersion = (binary, env) => {
|
|
729
|
+
try {
|
|
730
|
+
const probeEnv = { ...env };
|
|
731
|
+
for (const key of DISABLED_ENV) probeEnv[key] = '1';
|
|
732
|
+
const result = spawnSync(binary, ['--version'], {
|
|
733
|
+
encoding: 'utf8', env: probeEnv, timeout: VERSION_CHECK_TIMEOUT_MS,
|
|
734
|
+
});
|
|
735
|
+
if (result.error || result.status !== 0) return null;
|
|
736
|
+
return parseOpenCodeVersion(result.stdout);
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const compareOpenCodeVersions = (left, right) => {
|
|
743
|
+
const leftParts = left.split(/[+-]/, 1)[0].split('.').map(Number);
|
|
744
|
+
const rightParts = right.split(/[+-]/, 1)[0].split('.').map(Number);
|
|
745
|
+
for (let index = 0; index < 3; index += 1) {
|
|
746
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
747
|
+
return leftParts[index] > rightParts[index] ? 1 : -1;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return 0;
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const describeOpenCodeVersionMismatch = (installedVersion) => {
|
|
754
|
+
if (!installedVersion) return 'installed OpenCode version is unknown';
|
|
755
|
+
const relation = compareOpenCodeVersions(installedVersion, TESTED_OPENCODE_VERSION);
|
|
756
|
+
if (relation === 0) {
|
|
757
|
+
return `installed OpenCode ${installedVersion} differs from smoke-tested ${TESTED_OPENCODE_VERSION}`;
|
|
758
|
+
}
|
|
759
|
+
return `installed OpenCode ${installedVersion} is ${relation > 0 ? 'newer' : 'older'} than smoke-tested ${TESTED_OPENCODE_VERSION}`;
|
|
760
|
+
};
|
|
761
|
+
|
|
716
762
|
export default {
|
|
717
763
|
name: 'opencode',
|
|
718
764
|
runtimeType: 'opencode',
|
|
@@ -726,8 +772,7 @@ export default {
|
|
|
726
772
|
const res = spawnSync('opencode', ['--version'], { encoding: 'utf8' });
|
|
727
773
|
if (res.error || res.status !== 0) return null;
|
|
728
774
|
const stdout = (res.stdout || '').trim();
|
|
729
|
-
const
|
|
730
|
-
const version = versionMatch ? versionMatch[1] : (stdout || 'unknown');
|
|
775
|
+
const version = parseOpenCodeVersion(stdout) || stdout || 'unknown';
|
|
731
776
|
const where = spawnSync('which', ['opencode'], { encoding: 'utf8' });
|
|
732
777
|
const path = where.status === 0 ? (where.stdout || '').trim() || 'opencode' : 'opencode';
|
|
733
778
|
return { path, version };
|
|
@@ -761,6 +806,29 @@ export default {
|
|
|
761
806
|
}
|
|
762
807
|
const providerKeyFile = await validateProviderEnvironment(ctx.environment, ctx.cwd);
|
|
763
808
|
|
|
809
|
+
const sourceEnv = ctx.env || process.env;
|
|
810
|
+
const childSourceEnv = Object.fromEntries(Object.entries(sourceEnv)
|
|
811
|
+
.filter(([key]) => key !== ALLOW_UNTESTED_OPENCODE_VERSION_ENV));
|
|
812
|
+
const runtimeEnv = isPublic
|
|
813
|
+
? Object.fromEntries(Object.entries(childSourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
|
|
814
|
+
: childSourceEnv;
|
|
815
|
+
const binary = ctx._binaryPath || spawnBinaryPath(runtimeEnv);
|
|
816
|
+
const installedVersion = readOpenCodeVersion(binary, runtimeEnv);
|
|
817
|
+
if (installedVersion !== TESTED_OPENCODE_VERSION) {
|
|
818
|
+
const mismatch = describeOpenCodeVersionMismatch(installedVersion);
|
|
819
|
+
const allowOverride = process.env[ALLOW_UNTESTED_OPENCODE_VERSION_ENV] === '1';
|
|
820
|
+
if (isPublic && !allowOverride) {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`Public OpenCode seats refuse to run: ${mismatch}. `
|
|
823
|
+
+ `To override from the host process, set ${ALLOW_UNTESTED_OPENCODE_VERSION_ENV}=1.`,
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
// eslint-disable-next-line no-console
|
|
827
|
+
console.warn(isPublic
|
|
828
|
+
? `[opencode] ${mismatch}; continuing because the host process set ${ALLOW_UNTESTED_OPENCODE_VERSION_ENV}=1`
|
|
829
|
+
: `[opencode] ${mismatch}; trusted seat continues`);
|
|
830
|
+
}
|
|
831
|
+
|
|
764
832
|
const fullPrompt = buildMemoryPreamble(prompt, ctx.memoryLongTerm, {
|
|
765
833
|
freshSession: !ctx.sessionId,
|
|
766
834
|
});
|
|
@@ -769,9 +837,12 @@ export default {
|
|
|
769
837
|
const configPath = join(tempDir, 'opencode.json');
|
|
770
838
|
const configDir = join(tempDir, 'config-dir');
|
|
771
839
|
const xdgConfig = join(tempDir, 'xdg-config');
|
|
772
|
-
|
|
773
|
-
//
|
|
774
|
-
//
|
|
840
|
+
const home = isPublic ? join(tempDir, 'home') : null;
|
|
841
|
+
// OpenCode initializes its global and XDG config directories during
|
|
842
|
+
// startup, so create those roots and their ignore files before wrapping.
|
|
843
|
+
// Keep public HOME per-spawn, but leave $HOME/.opencode absent: OpenCode
|
|
844
|
+
// loads it after OPENCODE_CONFIG when present, which could persist config
|
|
845
|
+
// or MCP injection into later spawns.
|
|
775
846
|
const xdgConfigDir = join(xdgConfig, 'opencode');
|
|
776
847
|
await Promise.all([
|
|
777
848
|
mkdir(configDir),
|
|
@@ -831,10 +902,6 @@ export default {
|
|
|
831
902
|
});
|
|
832
903
|
await chmod(configPath, 0o600);
|
|
833
904
|
|
|
834
|
-
const sourceEnv = ctx.env || process.env;
|
|
835
|
-
const runtimeEnv = isPublic
|
|
836
|
-
? Object.fromEntries(Object.entries(sourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
|
|
837
|
-
: sourceEnv;
|
|
838
905
|
const childEnv = makeChildEnvironment({
|
|
839
906
|
source: runtimeEnv,
|
|
840
907
|
tempDir,
|
|
@@ -843,7 +910,7 @@ export default {
|
|
|
843
910
|
dataHome: openCodeHome.dataHome,
|
|
844
911
|
stateHome,
|
|
845
912
|
cacheHome,
|
|
846
|
-
home
|
|
913
|
+
home,
|
|
847
914
|
tempWork,
|
|
848
915
|
credentialFile: credential?.path || null,
|
|
849
916
|
});
|
|
@@ -855,7 +922,6 @@ export default {
|
|
|
855
922
|
effort: ctx.environment?.effort,
|
|
856
923
|
title: ctx.agentName,
|
|
857
924
|
});
|
|
858
|
-
const binary = ctx._binaryPath || spawnBinaryPath(childEnv);
|
|
859
925
|
const platform = ctx._platform || process.platform;
|
|
860
926
|
let cmd = binary;
|
|
861
927
|
let spawnArgs = args;
|
|
@@ -925,13 +991,16 @@ export default {
|
|
|
925
991
|
};
|
|
926
992
|
|
|
927
993
|
export {
|
|
994
|
+
ALLOW_UNTESTED_OPENCODE_VERSION_ENV,
|
|
928
995
|
DISABLED_ENV,
|
|
996
|
+
TESTED_OPENCODE_VERSION,
|
|
929
997
|
buildArgs,
|
|
930
998
|
buildMcpConfig,
|
|
931
999
|
buildProviderConfig,
|
|
932
1000
|
makeEventParser,
|
|
933
1001
|
prepareOpenCodeDataHome,
|
|
934
1002
|
publicPermissions,
|
|
1003
|
+
readOpenCodeVersion,
|
|
935
1004
|
qualifiedProviderModel,
|
|
936
1005
|
resolveProviderKeyFile,
|
|
937
1006
|
startProviderProxy,
|