opencode-azure-setup 1.0.0 → 1.0.1

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 +93 -41
  2. package/package.json +1 -1
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
- const home = os.homedir();
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,61 @@ function askPassword(question) {
84
79
  });
85
80
  }
86
81
 
87
- async function testConnection(endpoint, apiKey, deployment) {
88
- return new Promise((resolve) => {
89
- const url = new URL(`${endpoint}/deployments/${deployment}/chat/completions?api-version=2024-12-01-preview`);
82
+ // Parse Azure endpoint - handles both full URL and base URL
83
+ function parseAzureEndpoint(input) {
84
+ const result = {
85
+ baseUrl: '',
86
+ deployment: 'model-router',
87
+ apiVersion: '2025-01-01-preview', // Latest API version
88
+ };
89
+
90
+ try {
91
+ const url = new URL(input);
92
+
93
+ // Extract deployment from path: /openai/deployments/{deployment}/...
94
+ const deploymentMatch = url.pathname.match(/\/deployments\/([^/]+)/);
95
+ if (deploymentMatch) {
96
+ result.deployment = deploymentMatch[1];
97
+ }
98
+
99
+ // Extract api-version from query params
100
+ const apiVersion = url.searchParams.get('api-version');
101
+ if (apiVersion) {
102
+ result.apiVersion = apiVersion;
103
+ }
104
+
105
+ // Build base URL: https://host/openai
106
+ const pathParts = url.pathname.split('/');
107
+ const openaiIndex = pathParts.indexOf('openai');
108
+ if (openaiIndex !== -1) {
109
+ url.pathname = pathParts.slice(0, openaiIndex + 1).join('/');
110
+ } else {
111
+ url.pathname = '/openai';
112
+ }
113
+ url.search = '';
114
+ result.baseUrl = url.toString().replace(/\/$/, '');
115
+
116
+ } catch {
117
+ // Not a valid URL, assume it's just the host
118
+ let cleaned = input.replace(/\/$/, '');
119
+ if (!cleaned.startsWith('https://')) {
120
+ cleaned = 'https://' + cleaned;
121
+ }
122
+ if (!cleaned.endsWith('/openai')) {
123
+ cleaned += '/openai';
124
+ }
125
+ result.baseUrl = cleaned;
126
+ }
127
+
128
+ return result;
129
+ }
90
130
 
131
+ async function testConnection(endpoint, apiKey, deployment, apiVersion) {
132
+ return new Promise((resolve) => {
133
+ const url = new URL(`${endpoint}/deployments/${deployment}/chat/completions?api-version=${apiVersion}`);
91
134
  const options = {
92
135
  hostname: url.hostname,
93
- port: url.port || 443,
136
+ port: 443,
94
137
  path: url.pathname + url.search,
95
138
  method: 'POST',
96
139
  headers: {
@@ -111,7 +154,7 @@ async function testConnection(endpoint, apiKey, deployment) {
111
154
  });
112
155
 
113
156
  req.on('error', (e) => resolve({ ok: false, status: 0, body: e.message }));
114
- req.setTimeout(10000, () => {
157
+ req.setTimeout(15000, () => {
115
158
  req.destroy();
116
159
  resolve({ ok: false, status: 0, body: 'Timeout' });
117
160
  });
@@ -127,39 +170,37 @@ async function main() {
127
170
  console.log('─'.repeat(40));
128
171
  console.log();
129
172
 
130
- // Endpoint
131
- console.log('Enter your Azure OpenAI endpoint');
132
- console.log(colors.dim + '(from Azure Portal Azure OpenAI Keys and Endpoint)' + colors.reset);
133
- let endpoint = await ask('Endpoint');
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');
134
178
 
135
- if (!endpoint) {
179
+ if (!rawEndpoint) {
136
180
  console.log(colors.red + 'Endpoint is required' + colors.reset);
137
181
  process.exit(1);
138
182
  }
139
183
 
140
- endpoint = endpoint.replace(/\/$/, '');
141
- if (!endpoint.endsWith('/openai')) endpoint += '/openai';
142
-
143
- console.log();
184
+ // Parse the endpoint - extracts base URL, deployment, and api-version automatically
185
+ const parsed = parseAzureEndpoint(rawEndpoint);
144
186
 
145
187
  // API Key
188
+ console.log();
146
189
  const apiKey = await askPassword('API Key');
147
190
  if (!apiKey) {
148
191
  console.log(colors.red + 'API Key is required' + colors.reset);
149
192
  process.exit(1);
150
193
  }
151
194
 
152
- console.log();
153
-
154
- // Deployment
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');
195
+ // Use auto-detected values
196
+ let deployment = parsed.deployment;
197
+ let apiVersion = parsed.apiVersion;
158
198
 
159
199
  console.log();
160
200
  console.log(colors.blue + 'Testing connection...' + colors.reset);
201
+ console.log(colors.dim + ` ${parsed.baseUrl}/deployments/${deployment}` + colors.reset);
161
202
 
162
- const result = await testConnection(endpoint, apiKey, deployment);
203
+ let result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
163
204
 
164
205
  if (result.ok) {
165
206
  console.log(colors.green + '✓ Connection successful!' + colors.reset);
@@ -173,16 +214,29 @@ async function main() {
173
214
  console.log(colors.dim + result.body.slice(0, 200) + colors.reset);
174
215
  }
175
216
  }
217
+
218
+ // Offer to edit settings if connection failed
176
219
  console.log();
177
- const cont = await ask('Continue anyway? (y/N)', 'N');
178
- if (cont.toLowerCase() !== 'y') process.exit(1);
220
+ console.log(colors.yellow + 'Let\'s try different settings:' + colors.reset);
221
+ deployment = await ask('Deployment name', deployment);
222
+ apiVersion = await ask('API Version', apiVersion);
223
+
224
+ console.log();
225
+ console.log(colors.blue + 'Retrying...' + colors.reset);
226
+ result = await testConnection(parsed.baseUrl, apiKey, deployment, apiVersion);
227
+
228
+ if (result.ok) {
229
+ console.log(colors.green + '✓ Connection successful!' + colors.reset);
230
+ } else {
231
+ console.log(colors.red + `✗ Still failing (${result.status || 'error'})` + colors.reset);
232
+ const cont = await ask('Save config anyway? (y/N)', 'N');
233
+ if (cont.toLowerCase() !== 'y') process.exit(1);
234
+ }
179
235
  }
180
236
 
181
237
  // Create config
182
238
  const configPath = getConfigPath();
183
- const configDir = path.dirname(configPath);
184
-
185
- fs.mkdirSync(configDir, { recursive: true });
239
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
186
240
 
187
241
  const config = {
188
242
  $schema: 'https://opencode.ai/config.json',
@@ -192,10 +246,10 @@ async function main() {
192
246
  npm: '@ai-sdk/azure',
193
247
  name: 'Azure OpenAI',
194
248
  options: {
195
- baseURL: endpoint,
249
+ baseURL: parsed.baseUrl,
196
250
  apiKey: apiKey,
197
251
  useDeploymentBasedUrls: true,
198
- apiVersion: '2024-12-01-preview',
252
+ apiVersion: apiVersion,
199
253
  },
200
254
  models: {
201
255
  [deployment]: {
@@ -210,15 +264,13 @@ async function main() {
210
264
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
211
265
 
212
266
  console.log();
213
- console.log(colors.green + `✓ Configuration saved to ${configPath}` + colors.reset);
267
+ console.log(colors.green + '✓ Configuration saved!' + colors.reset);
268
+ console.log(colors.dim + ` ${configPath}` + colors.reset);
214
269
  console.log();
215
- console.log(colors.blue + "You're all set! Run:" + colors.reset);
216
- console.log();
217
- console.log(' opencode');
270
+ console.log('─'.repeat(40));
271
+ console.log(colors.green + 'You\'re all set! Run:' + colors.reset);
218
272
  console.log();
219
- console.log(colors.dim + 'Tips:' + colors.reset);
220
- console.log(' • View config: opencode azure status');
221
- console.log(' • Reconfigure: opencode azure');
273
+ console.log(' ' + colors.blue + 'opencode' + colors.reset);
222
274
  console.log();
223
275
 
224
276
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-azure-setup",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Easy Azure OpenAI setup for OpenCode",
5
5
  "type": "module",
6
6
  "main": "index.js",