@commonlyai/cli 0.1.94 → 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 +77 -11
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
|
});
|
|
@@ -834,10 +902,6 @@ export default {
|
|
|
834
902
|
});
|
|
835
903
|
await chmod(configPath, 0o600);
|
|
836
904
|
|
|
837
|
-
const sourceEnv = ctx.env || process.env;
|
|
838
|
-
const runtimeEnv = isPublic
|
|
839
|
-
? Object.fromEntries(Object.entries(sourceEnv).filter(([key]) => PUBLIC_SAFE_ENV.test(key)))
|
|
840
|
-
: sourceEnv;
|
|
841
905
|
const childEnv = makeChildEnvironment({
|
|
842
906
|
source: runtimeEnv,
|
|
843
907
|
tempDir,
|
|
@@ -858,7 +922,6 @@ export default {
|
|
|
858
922
|
effort: ctx.environment?.effort,
|
|
859
923
|
title: ctx.agentName,
|
|
860
924
|
});
|
|
861
|
-
const binary = ctx._binaryPath || spawnBinaryPath(childEnv);
|
|
862
925
|
const platform = ctx._platform || process.platform;
|
|
863
926
|
let cmd = binary;
|
|
864
927
|
let spawnArgs = args;
|
|
@@ -928,13 +991,16 @@ export default {
|
|
|
928
991
|
};
|
|
929
992
|
|
|
930
993
|
export {
|
|
994
|
+
ALLOW_UNTESTED_OPENCODE_VERSION_ENV,
|
|
931
995
|
DISABLED_ENV,
|
|
996
|
+
TESTED_OPENCODE_VERSION,
|
|
932
997
|
buildArgs,
|
|
933
998
|
buildMcpConfig,
|
|
934
999
|
buildProviderConfig,
|
|
935
1000
|
makeEventParser,
|
|
936
1001
|
prepareOpenCodeDataHome,
|
|
937
1002
|
publicPermissions,
|
|
1003
|
+
readOpenCodeVersion,
|
|
938
1004
|
qualifiedProviderModel,
|
|
939
1005
|
resolveProviderKeyFile,
|
|
940
1006
|
startProviderProxy,
|