opencode-azure-setup 1.0.1 → 1.0.3

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 +158 -44
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -37,6 +37,36 @@ function getConfigPath() {
37
37
  return path.join(os.homedir(), '.config', 'opencode', 'opencode.json');
38
38
  }
39
39
 
40
+ // Load existing config if it exists
41
+ function loadExistingConfig() {
42
+ const configPath = getConfigPath();
43
+ try {
44
+ if (fs.existsSync(configPath)) {
45
+ const content = fs.readFileSync(configPath, 'utf-8');
46
+ return JSON.parse(content);
47
+ }
48
+ } catch {
49
+ // Config doesn't exist or is invalid
50
+ }
51
+ return null;
52
+ }
53
+
54
+ // Extract existing Azure settings from config
55
+ function getExistingAzureSettings(config) {
56
+ if (!config?.provider?.azure?.options) return null;
57
+
58
+ const azure = config.provider.azure;
59
+ const opts = azure.options;
60
+ const modelName = Object.keys(azure.models || {})[0] || 'model-router';
61
+
62
+ return {
63
+ baseUrl: opts.baseURL || '',
64
+ apiKey: opts.apiKey || '',
65
+ deployment: modelName,
66
+ apiVersion: opts.apiVersion || '2025-01-01-preview',
67
+ };
68
+ }
69
+
40
70
  const rl = readline.createInterface({
41
71
  input: process.stdin,
42
72
  output: process.stdout,
@@ -49,9 +79,15 @@ function ask(question, defaultValue = '') {
49
79
  });
50
80
  }
51
81
 
52
- function askPassword(question) {
82
+ function askPassword(question, existingValue = '') {
53
83
  return new Promise((resolve) => {
54
- process.stdout.write(`${question}: `);
84
+ if (existingValue) {
85
+ const masked = existingValue.slice(0, 4) + '...' + existingValue.slice(-4);
86
+ process.stdout.write(`${question} [${masked}]: `);
87
+ } else {
88
+ process.stdout.write(`${question}: `);
89
+ }
90
+
55
91
  if (process.stdin.isTTY) {
56
92
  const stdin = process.stdin;
57
93
  stdin.setRawMode(true);
@@ -63,7 +99,8 @@ function askPassword(question) {
63
99
  stdin.setRawMode(false);
64
100
  stdin.removeListener('data', onData);
65
101
  console.log();
66
- resolve(password);
102
+ // If user just pressed Enter and there's an existing value, use it
103
+ resolve(password || existingValue);
67
104
  } else if (char === '\u0003') {
68
105
  process.exit();
69
106
  } else if (char === '\u007F' || char === '\b') {
@@ -74,17 +111,40 @@ function askPassword(question) {
74
111
  };
75
112
  stdin.on('data', onData);
76
113
  } else {
77
- rl.question('', resolve);
114
+ rl.question('', (answer) => resolve(answer || existingValue));
78
115
  }
79
116
  });
80
117
  }
81
118
 
119
+ // Fetch latest defaults from GitHub (falls back to hardcoded if offline)
120
+ async function fetchDefaults() {
121
+ const defaults = {
122
+ deployment: 'model-router',
123
+ apiVersion: '2025-01-01-preview',
124
+ };
125
+
126
+ try {
127
+ const res = await fetch('https://raw.githubusercontent.com/schwarztim/opencode/dev/azure-defaults.json', {
128
+ signal: AbortSignal.timeout(3000),
129
+ });
130
+ if (res.ok) {
131
+ const data = await res.json();
132
+ if (data.apiVersion) defaults.apiVersion = data.apiVersion;
133
+ if (data.deployment) defaults.deployment = data.deployment;
134
+ }
135
+ } catch {
136
+ // Offline or fetch failed - use hardcoded defaults
137
+ }
138
+
139
+ return defaults;
140
+ }
141
+
82
142
  // Parse Azure endpoint - handles both full URL and base URL
83
- function parseAzureEndpoint(input) {
143
+ function parseAzureEndpoint(input, defaults) {
84
144
  const result = {
85
145
  baseUrl: '',
86
- deployment: 'model-router',
87
- apiVersion: '2025-01-01-preview', // Latest API version
146
+ deployment: defaults.deployment,
147
+ apiVersion: defaults.apiVersion,
88
148
  };
89
149
 
90
150
  try {
@@ -170,37 +230,77 @@ async function main() {
170
230
  console.log('─'.repeat(40));
171
231
  console.log();
172
232
 
173
- // Endpoint - accepts full URL or just the base
174
- console.log('Paste your Azure OpenAI endpoint');
175
- console.log(colors.dim + 'Tip: You can paste the full URL from Azure Portal - we\'ll extract what we need' + colors.reset);
176
- console.log();
177
- const rawEndpoint = await ask('Endpoint');
233
+ // Load existing config
234
+ const existingConfig = loadExistingConfig();
235
+ const existingAzure = getExistingAzureSettings(existingConfig);
178
236
 
179
- if (!rawEndpoint) {
180
- console.log(colors.red + 'Endpoint is required' + colors.reset);
181
- process.exit(1);
237
+ // Fetch latest defaults (non-blocking, falls back to hardcoded)
238
+ const defaults = await fetchDefaults();
239
+
240
+ // If existing config found, show current values
241
+ if (existingAzure && existingAzure.baseUrl) {
242
+ console.log(colors.green + '✓ Existing configuration found' + colors.reset);
243
+ console.log(colors.dim + ' Press Enter to keep current values, or type new ones' + colors.reset);
244
+ console.log();
182
245
  }
183
246
 
184
- // Parse the endpoint - extracts base URL, deployment, and api-version automatically
185
- const parsed = parseAzureEndpoint(rawEndpoint);
247
+ // Endpoint - accepts full URL or just the base
248
+ let baseUrl, deployment, apiVersion;
249
+
250
+ if (existingAzure?.baseUrl) {
251
+ console.log('Azure OpenAI Endpoint');
252
+ const rawEndpoint = await ask('Endpoint', existingAzure.baseUrl);
253
+
254
+ if (rawEndpoint === existingAzure.baseUrl) {
255
+ // User kept existing - use existing parsed values
256
+ baseUrl = existingAzure.baseUrl;
257
+ deployment = existingAzure.deployment;
258
+ apiVersion = existingAzure.apiVersion;
259
+ } else {
260
+ // User entered new value - parse it
261
+ const parsed = parseAzureEndpoint(rawEndpoint, defaults);
262
+ baseUrl = parsed.baseUrl;
263
+ deployment = parsed.deployment;
264
+ apiVersion = parsed.apiVersion;
265
+ }
266
+ } else {
267
+ console.log('Paste your Azure OpenAI endpoint');
268
+ console.log(colors.dim + 'Tip: You can paste the full URL from Azure Portal - we\'ll extract what we need' + colors.reset);
269
+ console.log();
270
+ const rawEndpoint = await ask('Endpoint');
271
+
272
+ if (!rawEndpoint) {
273
+ console.log(colors.red + 'Endpoint is required' + colors.reset);
274
+ process.exit(1);
275
+ }
276
+
277
+ const parsed = parseAzureEndpoint(rawEndpoint, defaults);
278
+ baseUrl = parsed.baseUrl;
279
+ deployment = parsed.deployment;
280
+ apiVersion = parsed.apiVersion;
281
+ }
186
282
 
187
283
  // API Key
188
284
  console.log();
189
- const apiKey = await askPassword('API Key');
285
+ const apiKey = await askPassword('API Key', existingAzure?.apiKey || '');
190
286
  if (!apiKey) {
191
287
  console.log(colors.red + 'API Key is required' + colors.reset);
192
288
  process.exit(1);
193
289
  }
194
290
 
195
- // Use auto-detected values
196
- let deployment = parsed.deployment;
197
- let apiVersion = parsed.apiVersion;
291
+ // Deployment (only ask if not using existing)
292
+ if (existingAzure?.deployment && deployment === existingAzure.deployment) {
293
+ // Keep existing
294
+ } else {
295
+ console.log();
296
+ deployment = await ask('Deployment name', deployment);
297
+ }
198
298
 
199
299
  console.log();
200
300
  console.log(colors.blue + 'Testing connection...' + colors.reset);
201
- console.log(colors.dim + ` ${parsed.baseUrl}/deployments/${deployment}` + colors.reset);
301
+ console.log(colors.dim + ` ${baseUrl}/deployments/${deployment}` + colors.reset);
202
302
 
203
- let result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
303
+ let result = await testConnection(baseUrl, apiKey, deployment, apiVersion);
204
304
 
205
305
  if (result.ok) {
206
306
  console.log(colors.green + '✓ Connection successful!' + colors.reset);
@@ -223,7 +323,7 @@ async function main() {
223
323
 
224
324
  console.log();
225
325
  console.log(colors.blue + 'Retrying...' + colors.reset);
226
- result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
326
+ result = await testConnection(baseUrl, apiKey, deployment, apiVersion);
227
327
 
228
328
  if (result.ok) {
229
329
  console.log(colors.green + '✓ Connection successful!' + colors.reset);
@@ -234,29 +334,32 @@ async function main() {
234
334
  }
235
335
  }
236
336
 
237
- // Create config
337
+ // Create config - preserve existing settings like agents, permissions
238
338
  const configPath = getConfigPath();
239
339
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
240
340
 
241
- const config = {
242
- $schema: 'https://opencode.ai/config.json',
243
- model: `azure/${deployment}`,
244
- provider: {
245
- azure: {
246
- npm: '@ai-sdk/azure',
247
- name: 'Azure OpenAI',
248
- options: {
249
- baseURL: parsed.baseUrl,
250
- apiKey: apiKey,
251
- useDeploymentBasedUrls: true,
252
- apiVersion: apiVersion,
253
- },
254
- models: {
255
- [deployment]: {
256
- name: deployment,
257
- limit: { context: 200000, output: 16384 },
258
- },
259
- },
341
+ // Start with existing config or empty object
342
+ const config = existingConfig || {};
343
+
344
+ // Update schema and model
345
+ config.$schema = 'https://opencode.ai/config.json';
346
+ config.model = `azure/${deployment}`;
347
+
348
+ // Update Azure provider settings
349
+ config.provider = config.provider || {};
350
+ config.provider.azure = {
351
+ npm: '@ai-sdk/azure',
352
+ name: 'Azure OpenAI',
353
+ options: {
354
+ baseURL: baseUrl,
355
+ apiKey: apiKey,
356
+ useDeploymentBasedUrls: true,
357
+ apiVersion: apiVersion,
358
+ },
359
+ models: {
360
+ [deployment]: {
361
+ name: deployment,
362
+ limit: { context: 200000, output: 16384 },
260
363
  },
261
364
  },
262
365
  };
@@ -266,6 +369,17 @@ async function main() {
266
369
  console.log();
267
370
  console.log(colors.green + '✓ Configuration saved!' + colors.reset);
268
371
  console.log(colors.dim + ` ${configPath}` + colors.reset);
372
+
373
+ // Show what was preserved
374
+ if (existingConfig) {
375
+ const preserved = [];
376
+ if (existingConfig.agent) preserved.push('agents');
377
+ if (existingConfig.permission) preserved.push('permissions');
378
+ if (preserved.length > 0) {
379
+ console.log(colors.dim + ` Preserved: ${preserved.join(', ')}` + colors.reset);
380
+ }
381
+ }
382
+
269
383
  console.log();
270
384
  console.log('─'.repeat(40));
271
385
  console.log(colors.green + 'You\'re all set! Run:' + colors.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-azure-setup",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Easy Azure OpenAI setup for OpenCode",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -22,7 +22,7 @@
22
22
  "license": "MIT",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "https://github.com/schwarztim/opencode.git"
25
+ "url": "git+https://github.com/schwarztim/opencode.git"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=18"