opencode-azure-setup 1.0.0 → 1.0.2
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 +119 -41
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -33,13 +33,10 @@ const logo = `
|
|
|
33
33
|
|_| Azure Edition
|
|
34
34
|
`;
|
|
35
35
|
|
|
36
|
-
// Get config path
|
|
37
36
|
function getConfigPath() {
|
|
38
|
-
|
|
39
|
-
return path.join(home, '.config', 'opencode', 'opencode.json');
|
|
37
|
+
return path.join(os.homedir(), '.config', 'opencode', 'opencode.json');
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
// Readline interface
|
|
43
40
|
const rl = readline.createInterface({
|
|
44
41
|
input: process.stdin,
|
|
45
42
|
output: process.stdout,
|
|
@@ -55,13 +52,11 @@ function ask(question, defaultValue = '') {
|
|
|
55
52
|
function askPassword(question) {
|
|
56
53
|
return new Promise((resolve) => {
|
|
57
54
|
process.stdout.write(`${question}: `);
|
|
58
|
-
|
|
59
55
|
if (process.stdin.isTTY) {
|
|
60
56
|
const stdin = process.stdin;
|
|
61
57
|
stdin.setRawMode(true);
|
|
62
58
|
stdin.resume();
|
|
63
59
|
stdin.setEncoding('utf8');
|
|
64
|
-
|
|
65
60
|
let password = '';
|
|
66
61
|
const onData = (char) => {
|
|
67
62
|
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
@@ -84,13 +79,84 @@ function askPassword(question) {
|
|
|
84
79
|
});
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
// Fetch latest defaults from GitHub (falls back to hardcoded if offline)
|
|
83
|
+
async function fetchDefaults() {
|
|
84
|
+
const defaults = {
|
|
85
|
+
deployment: 'model-router',
|
|
86
|
+
apiVersion: '2025-01-01-preview',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch('https://raw.githubusercontent.com/schwarztim/opencode/dev/azure-defaults.json', {
|
|
91
|
+
signal: AbortSignal.timeout(3000),
|
|
92
|
+
});
|
|
93
|
+
if (res.ok) {
|
|
94
|
+
const data = await res.json();
|
|
95
|
+
if (data.apiVersion) defaults.apiVersion = data.apiVersion;
|
|
96
|
+
if (data.deployment) defaults.deployment = data.deployment;
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Offline or fetch failed - use hardcoded defaults
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return defaults;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse Azure endpoint - handles both full URL and base URL
|
|
106
|
+
function parseAzureEndpoint(input, defaults) {
|
|
107
|
+
const result = {
|
|
108
|
+
baseUrl: '',
|
|
109
|
+
deployment: defaults.deployment,
|
|
110
|
+
apiVersion: defaults.apiVersion,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(input);
|
|
115
|
+
|
|
116
|
+
// Extract deployment from path: /openai/deployments/{deployment}/...
|
|
117
|
+
const deploymentMatch = url.pathname.match(/\/deployments\/([^/]+)/);
|
|
118
|
+
if (deploymentMatch) {
|
|
119
|
+
result.deployment = deploymentMatch[1];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Extract api-version from query params
|
|
123
|
+
const apiVersion = url.searchParams.get('api-version');
|
|
124
|
+
if (apiVersion) {
|
|
125
|
+
result.apiVersion = apiVersion;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build base URL: https://host/openai
|
|
129
|
+
const pathParts = url.pathname.split('/');
|
|
130
|
+
const openaiIndex = pathParts.indexOf('openai');
|
|
131
|
+
if (openaiIndex !== -1) {
|
|
132
|
+
url.pathname = pathParts.slice(0, openaiIndex + 1).join('/');
|
|
133
|
+
} else {
|
|
134
|
+
url.pathname = '/openai';
|
|
135
|
+
}
|
|
136
|
+
url.search = '';
|
|
137
|
+
result.baseUrl = url.toString().replace(/\/$/, '');
|
|
138
|
+
|
|
139
|
+
} catch {
|
|
140
|
+
// Not a valid URL, assume it's just the host
|
|
141
|
+
let cleaned = input.replace(/\/$/, '');
|
|
142
|
+
if (!cleaned.startsWith('https://')) {
|
|
143
|
+
cleaned = 'https://' + cleaned;
|
|
144
|
+
}
|
|
145
|
+
if (!cleaned.endsWith('/openai')) {
|
|
146
|
+
cleaned += '/openai';
|
|
147
|
+
}
|
|
148
|
+
result.baseUrl = cleaned;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
90
153
|
|
|
154
|
+
async function testConnection(endpoint, apiKey, deployment, apiVersion) {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
const url = new URL(`${endpoint}/deployments/${deployment}/chat/completions?api-version=${apiVersion}`);
|
|
91
157
|
const options = {
|
|
92
158
|
hostname: url.hostname,
|
|
93
|
-
port:
|
|
159
|
+
port: 443,
|
|
94
160
|
path: url.pathname + url.search,
|
|
95
161
|
method: 'POST',
|
|
96
162
|
headers: {
|
|
@@ -111,7 +177,7 @@ async function testConnection(endpoint, apiKey, deployment) {
|
|
|
111
177
|
});
|
|
112
178
|
|
|
113
179
|
req.on('error', (e) => resolve({ ok: false, status: 0, body: e.message }));
|
|
114
|
-
req.setTimeout(
|
|
180
|
+
req.setTimeout(15000, () => {
|
|
115
181
|
req.destroy();
|
|
116
182
|
resolve({ ok: false, status: 0, body: 'Timeout' });
|
|
117
183
|
});
|
|
@@ -127,39 +193,40 @@ async function main() {
|
|
|
127
193
|
console.log('─'.repeat(40));
|
|
128
194
|
console.log();
|
|
129
195
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
console.log(colors.dim + '(from Azure Portal → Azure OpenAI → Keys and Endpoint)' + colors.reset);
|
|
133
|
-
let endpoint = await ask('Endpoint');
|
|
196
|
+
// Fetch latest defaults (non-blocking, falls back to hardcoded)
|
|
197
|
+
const defaults = await fetchDefaults();
|
|
134
198
|
|
|
135
|
-
|
|
199
|
+
// Endpoint - accepts full URL or just the base
|
|
200
|
+
console.log('Paste your Azure OpenAI endpoint');
|
|
201
|
+
console.log(colors.dim + 'Tip: You can paste the full URL from Azure Portal - we\'ll extract what we need' + colors.reset);
|
|
202
|
+
console.log();
|
|
203
|
+
const rawEndpoint = await ask('Endpoint');
|
|
204
|
+
|
|
205
|
+
if (!rawEndpoint) {
|
|
136
206
|
console.log(colors.red + 'Endpoint is required' + colors.reset);
|
|
137
207
|
process.exit(1);
|
|
138
208
|
}
|
|
139
209
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
console.log();
|
|
210
|
+
// Parse the endpoint - extracts base URL, deployment, and api-version automatically
|
|
211
|
+
const parsed = parseAzureEndpoint(rawEndpoint, defaults);
|
|
144
212
|
|
|
145
213
|
// API Key
|
|
214
|
+
console.log();
|
|
146
215
|
const apiKey = await askPassword('API Key');
|
|
147
216
|
if (!apiKey) {
|
|
148
217
|
console.log(colors.red + 'API Key is required' + colors.reset);
|
|
149
218
|
process.exit(1);
|
|
150
219
|
}
|
|
151
220
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log('Enter your deployment name');
|
|
156
|
-
console.log(colors.dim + '(default: model-router for Azure APIM setups)' + colors.reset);
|
|
157
|
-
const deployment = await ask('Deployment', 'model-router');
|
|
221
|
+
// Use auto-detected values
|
|
222
|
+
let deployment = parsed.deployment;
|
|
223
|
+
let apiVersion = parsed.apiVersion;
|
|
158
224
|
|
|
159
225
|
console.log();
|
|
160
226
|
console.log(colors.blue + 'Testing connection...' + colors.reset);
|
|
227
|
+
console.log(colors.dim + ` ${parsed.baseUrl}/deployments/${deployment}` + colors.reset);
|
|
161
228
|
|
|
162
|
-
|
|
229
|
+
let result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
|
|
163
230
|
|
|
164
231
|
if (result.ok) {
|
|
165
232
|
console.log(colors.green + '✓ Connection successful!' + colors.reset);
|
|
@@ -173,16 +240,29 @@ async function main() {
|
|
|
173
240
|
console.log(colors.dim + result.body.slice(0, 200) + colors.reset);
|
|
174
241
|
}
|
|
175
242
|
}
|
|
243
|
+
|
|
244
|
+
// Offer to edit settings if connection failed
|
|
176
245
|
console.log();
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
console.log(colors.yellow + 'Let\'s try different settings:' + colors.reset);
|
|
247
|
+
deployment = await ask('Deployment name', deployment);
|
|
248
|
+
apiVersion = await ask('API Version', apiVersion);
|
|
249
|
+
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(colors.blue + 'Retrying...' + colors.reset);
|
|
252
|
+
result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
|
|
253
|
+
|
|
254
|
+
if (result.ok) {
|
|
255
|
+
console.log(colors.green + '✓ Connection successful!' + colors.reset);
|
|
256
|
+
} else {
|
|
257
|
+
console.log(colors.red + `✗ Still failing (${result.status || 'error'})` + colors.reset);
|
|
258
|
+
const cont = await ask('Save config anyway? (y/N)', 'N');
|
|
259
|
+
if (cont.toLowerCase() !== 'y') process.exit(1);
|
|
260
|
+
}
|
|
179
261
|
}
|
|
180
262
|
|
|
181
263
|
// Create config
|
|
182
264
|
const configPath = getConfigPath();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
265
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
186
266
|
|
|
187
267
|
const config = {
|
|
188
268
|
$schema: 'https://opencode.ai/config.json',
|
|
@@ -192,10 +272,10 @@ async function main() {
|
|
|
192
272
|
npm: '@ai-sdk/azure',
|
|
193
273
|
name: 'Azure OpenAI',
|
|
194
274
|
options: {
|
|
195
|
-
baseURL:
|
|
275
|
+
baseURL: parsed.baseUrl,
|
|
196
276
|
apiKey: apiKey,
|
|
197
277
|
useDeploymentBasedUrls: true,
|
|
198
|
-
apiVersion:
|
|
278
|
+
apiVersion: apiVersion,
|
|
199
279
|
},
|
|
200
280
|
models: {
|
|
201
281
|
[deployment]: {
|
|
@@ -210,15 +290,13 @@ async function main() {
|
|
|
210
290
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
211
291
|
|
|
212
292
|
console.log();
|
|
213
|
-
console.log(colors.green +
|
|
293
|
+
console.log(colors.green + '✓ Configuration saved!' + colors.reset);
|
|
294
|
+
console.log(colors.dim + ` ${configPath}` + colors.reset);
|
|
214
295
|
console.log();
|
|
215
|
-
console.log(
|
|
216
|
-
console.log();
|
|
217
|
-
console.log(' opencode');
|
|
296
|
+
console.log('─'.repeat(40));
|
|
297
|
+
console.log(colors.green + 'You\'re all set! Run:' + colors.reset);
|
|
218
298
|
console.log();
|
|
219
|
-
console.log(colors.
|
|
220
|
-
console.log(' • View config: opencode azure status');
|
|
221
|
-
console.log(' • Reconfigure: opencode azure');
|
|
299
|
+
console.log(' ' + colors.blue + 'opencode' + colors.reset);
|
|
222
300
|
console.log();
|
|
223
301
|
|
|
224
302
|
rl.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-azure-setup",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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"
|