opencode-studio-server 1.19.0 → 1.21.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.
Files changed (2) hide show
  1. package/index.js +122 -104
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1027,37 +1027,30 @@ app.post('/api/ohmyopencode', (req, res) => {
1027
1027
  return res.status(400).json({ error: 'Missing preferences.agents' });
1028
1028
  }
1029
1029
 
1030
- // Save preferences to studio.json
1031
1030
  const studio = loadStudioConfig();
1032
1031
  studio.ohmy = preferences;
1033
1032
  saveStudioConfig(studio);
1034
1033
 
1035
- // Load current oh-my-opencode.json or start fresh
1036
1034
  const currentConfig = loadOhMyOpenCodeConfig() || {};
1037
1035
  const warnings = [];
1038
1036
 
1039
- // For each agent, pick first available model and apply thinking/reasoning
1040
1037
  for (const [agentName, agentPrefs] of Object.entries(preferences.agents)) {
1041
1038
  const choices = agentPrefs.choices || [];
1042
1039
  const available = choices.find(c => c.available);
1043
1040
  if (available) {
1044
- // Set the model in oh-my-opencode.json
1045
1041
  if (!currentConfig.agents) currentConfig.agents = {};
1046
1042
  const agentConfig = { model: available.model };
1047
1043
 
1048
- // Add thinking config if enabled (for Gemini models)
1049
- if (agentPrefs.thinking && agentPrefs.thinking.type === 'enabled') {
1044
+ if (available.thinking && available.thinking.type === 'enabled') {
1050
1045
  agentConfig.thinking = { type: 'enabled' };
1051
1046
  }
1052
1047
 
1053
- // Add reasoning config if set (for OpenAI o-series models)
1054
- if (agentPrefs.reasoning && agentPrefs.reasoning.effort) {
1055
- agentConfig.reasoning = { effort: agentPrefs.reasoning.effort };
1048
+ if (available.reasoning && available.reasoning.effort) {
1049
+ agentConfig.reasoning = { effort: available.reasoning.effort };
1056
1050
  }
1057
1051
 
1058
1052
  currentConfig.agents[agentName] = agentConfig;
1059
1053
  } else if (choices.length > 0) {
1060
- // No available model, keep existing or warn
1061
1054
  warnings.push(`No available model for agent "${agentName}"`);
1062
1055
  }
1063
1056
  }
@@ -1144,40 +1137,34 @@ async function ensureGitHubRepo(token, repoName) {
1144
1137
  throw new Error(`Failed to check repo: ${err}`);
1145
1138
  }
1146
1139
 
1147
- function buildCommitTree(opencodeDir, studioDir, files = {}, basePath = '') {
1148
- const tree = [];
1149
-
1150
- for (const name of fs.readdirSync(basePath || opencodeDir)) {
1151
- const fullPath = path.join(basePath || opencodeDir, name);
1152
- const stat = fs.statSync(fullPath);
1153
-
1154
- if (stat.isDirectory()) {
1155
- if (name === 'node_modules' || name === '.git' || name === '.next') continue;
1156
- const subtree = buildCommitTree(opencodeDir, studioDir, files, fullPath);
1157
- tree.push({
1158
- path: path.relative(opencodeDir, fullPath),
1159
- mode: '040000',
1160
- type: 'tree',
1161
- sha: subtree.sha,
1162
- size: subtree.size
1163
- });
1164
- if (subtree.size) tree[tree.length - 1].size = subtree.size;
1165
- } else {
1166
- if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
1167
- const content = fs.readFileSync(fullPath, 'utf8');
1168
- const size = Buffer.byteLength(content);
1169
- const blob = { path: path.relative(opencodeDir, fullPath), mode: '100644', type: 'blob', size, content };
1170
- const blobKey = `${basePath || opencodeDir}/${name}`;
1171
- files[blobKey] = blob;
1172
- tree.push(blob);
1173
- }
1174
- }
1175
-
1176
- return { sha: null, tree };
1177
- }
1140
+ function collectBlobs(rootDir, basePath = '', blobs = []) {
1141
+ const dir = basePath || rootDir;
1142
+ if (!fs.existsSync(dir)) return blobs;
1143
+
1144
+ for (const name of fs.readdirSync(dir)) {
1145
+ const fullPath = path.join(dir, name);
1146
+ const stat = fs.statSync(fullPath);
1147
+
1148
+ if (stat.isDirectory()) {
1149
+ if (name === 'node_modules' || name === '.git' || name === '.next') continue;
1150
+ collectBlobs(rootDir, fullPath, blobs);
1151
+ } else {
1152
+ if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
1153
+ const content = fs.readFileSync(fullPath, 'utf8');
1154
+ blobs.push({
1155
+ path: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
1156
+ mode: '100644',
1157
+ type: 'blob',
1158
+ content
1159
+ });
1160
+ }
1161
+ }
1162
+ return blobs;
1163
+ }
1178
1164
 
1179
- async function createGitHubBlob(token, blob) {
1180
- const response = await fetch('https://api.github.com/git/blobs', {
1165
+ async function createGitHubBlob(token, repoName, blob) {
1166
+ const [owner, repo] = repoName.split('/');
1167
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, {
1181
1168
  method: 'POST',
1182
1169
  headers: {
1183
1170
  'Authorization': `Bearer ${token}`,
@@ -1198,32 +1185,35 @@ async function createGitHubBlob(token, blob) {
1198
1185
  return data.sha;
1199
1186
  }
1200
1187
 
1201
- async function createGitHubTree(token, repoName, treeItems) {
1202
- const [owner, repo] = repoName.split('/');
1203
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
1204
- method: 'POST',
1205
- headers: {
1206
- 'Authorization': `Bearer ${token}`,
1207
- 'Content-Type': 'application/json'
1208
- },
1209
- body: JSON.stringify({
1210
- tree: treeItems.map(item => ({
1211
- path: item.path,
1212
- mode: item.mode,
1213
- type: item.type,
1214
- sha: item.sha
1215
- }))
1216
- })
1217
- });
1218
-
1219
- if (!response.ok) {
1220
- const err = await response.text();
1221
- throw new Error(`Failed to create tree: ${err}`);
1222
- }
1223
-
1224
- const data = await response.json();
1225
- return data.sha;
1226
- }
1188
+ async function createGitHubTree(token, repoName, treeItems, baseSha = null) {
1189
+ const [owner, repo] = repoName.split('/');
1190
+ const body = {
1191
+ tree: treeItems.map(item => ({
1192
+ path: item.path,
1193
+ mode: item.mode,
1194
+ type: item.type,
1195
+ sha: item.sha
1196
+ }))
1197
+ };
1198
+ if (baseSha) body.base_tree = baseSha;
1199
+
1200
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
1201
+ method: 'POST',
1202
+ headers: {
1203
+ 'Authorization': `Bearer ${token}`,
1204
+ 'Content-Type': 'application/json'
1205
+ },
1206
+ body: JSON.stringify(body)
1207
+ });
1208
+
1209
+ if (!response.ok) {
1210
+ const err = await response.text();
1211
+ throw new Error(`Failed to create tree: ${err}`);
1212
+ }
1213
+
1214
+ const data = await response.json();
1215
+ return data.sha;
1216
+ }
1227
1217
 
1228
1218
  async function createGitHubCommit(token, repoName, treeSha, message) {
1229
1219
  const [owner, repo] = repoName.split('/');
@@ -1260,25 +1250,49 @@ async function createGitHubCommit(token, repoName, treeSha, message) {
1260
1250
  return data.sha;
1261
1251
  }
1262
1252
 
1263
- async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
1264
- const [owner, repo] = repoName.split('/');
1265
-
1266
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1267
- method: 'PATCH',
1268
- headers: {
1269
- 'Authorization': `Bearer ${token}`,
1270
- 'Content-Type': 'application/json'
1271
- },
1272
- body: JSON.stringify({
1273
- sha: commitSha
1274
- })
1275
- });
1276
-
1277
- if (!response.status === 200 && response.status !== 201) {
1278
- const err = await response.text();
1279
- throw new Error(`Failed to update ref: ${err}`);
1280
- }
1281
- }
1253
+ async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
1254
+ const [owner, repo] = repoName.split('/');
1255
+
1256
+ const checkRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1257
+ headers: { 'Authorization': `Bearer ${token}` }
1258
+ });
1259
+
1260
+ if (checkRes.status === 404) {
1261
+ const createRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
1262
+ method: 'POST',
1263
+ headers: {
1264
+ 'Authorization': `Bearer ${token}`,
1265
+ 'Content-Type': 'application/json'
1266
+ },
1267
+ body: JSON.stringify({
1268
+ ref: `refs/heads/${branch}`,
1269
+ sha: commitSha
1270
+ })
1271
+ });
1272
+
1273
+ if (!createRes.ok) {
1274
+ const err = await createRes.text();
1275
+ throw new Error(`Failed to create ref: ${err}`);
1276
+ }
1277
+ return;
1278
+ }
1279
+
1280
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1281
+ method: 'PATCH',
1282
+ headers: {
1283
+ 'Authorization': `Bearer ${token}`,
1284
+ 'Content-Type': 'application/json'
1285
+ },
1286
+ body: JSON.stringify({
1287
+ sha: commitSha
1288
+ })
1289
+ });
1290
+
1291
+ if (!response.ok) {
1292
+ const err = await response.text();
1293
+ throw new Error(`Failed to update ref: ${err}`);
1294
+ }
1295
+ }
1282
1296
 
1283
1297
  app.get('/api/github/backup/status', async (req, res) => {
1284
1298
  try {
@@ -1335,22 +1349,26 @@ app.post('/api/github/backup', async (req, res) => {
1335
1349
  const opencodeConfig = getConfigPath();
1336
1350
  if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
1337
1351
 
1338
- const opencodeDir = path.dirname(opencodeConfig);
1339
- const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
1340
-
1341
- const blobs = {};
1342
- const opencodeTree = buildCommitTree(opencodeDir, studioDir, blobs);
1343
- const studioTree = buildCommitTree(studioDir, studioDir, {}, studioDir);
1344
-
1345
- const opencodeTreeSha = await createGitHubTree(token, repoName, opencodeTree.tree);
1346
- const studioTreeSha = await createGitHubTree(token, repoName, studioTree.tree);
1347
-
1348
- const rootTreeItems = [
1349
- { path: 'opencode', mode: '040000', type: 'tree', sha: opencodeTreeSha },
1350
- { path: 'opencode-studio', mode: '040000', type: 'tree', sha: studioTreeSha }
1351
- ];
1352
-
1353
- const rootTreeSha = await createGitHubTree(token, repoName, rootTreeItems);
1352
+ const opencodeDir = path.dirname(opencodeConfig);
1353
+ const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
1354
+
1355
+ const opencodeBlobs = collectBlobs(opencodeDir);
1356
+ const studioBlobs = collectBlobs(studioDir);
1357
+
1358
+ for (const blob of opencodeBlobs) {
1359
+ blob.sha = await createGitHubBlob(token, repoName, blob);
1360
+ blob.path = `opencode/${blob.path}`;
1361
+ delete blob.content;
1362
+ }
1363
+
1364
+ for (const blob of studioBlobs) {
1365
+ blob.sha = await createGitHubBlob(token, repoName, blob);
1366
+ blob.path = `opencode-studio/${blob.path}`;
1367
+ delete blob.content;
1368
+ }
1369
+
1370
+ const allBlobs = [...opencodeBlobs, ...studioBlobs];
1371
+ const rootTreeSha = await createGitHubTree(token, repoName, allBlobs);
1354
1372
 
1355
1373
  const timestamp = new Date().toISOString();
1356
1374
  const commitMessage = `OpenCode Studio backup ${timestamp}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {