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.
- package/index.js +158 -44
- 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
|
-
|
|
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
|
-
|
|
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:
|
|
87
|
-
apiVersion:
|
|
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
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
//
|
|
185
|
-
|
|
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
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
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 + ` ${
|
|
301
|
+
console.log(colors.dim + ` ${baseUrl}/deployments/${deployment}` + colors.reset);
|
|
202
302
|
|
|
203
|
-
let result = await testConnection(
|
|
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(
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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.
|
|
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"
|