opencode-azure-setup 1.0.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.
- package/index.js +230 -0
- package/package.json +30 -0
package/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenCode Azure Setup
|
|
4
|
+
* Cross-platform installer for Azure OpenAI configuration
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx opencode-azure-setup
|
|
8
|
+
* node install-azure.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import readline from 'readline';
|
|
14
|
+
import https from 'https';
|
|
15
|
+
import os from 'os';
|
|
16
|
+
|
|
17
|
+
// Colors
|
|
18
|
+
const colors = {
|
|
19
|
+
blue: '\x1b[34m',
|
|
20
|
+
green: '\x1b[32m',
|
|
21
|
+
yellow: '\x1b[33m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
dim: '\x1b[2m',
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const logo = `
|
|
28
|
+
___ ____ _
|
|
29
|
+
/ _ \\ _ __ ___ _ __ / ___|___ __| | ___
|
|
30
|
+
| | | | '_ \\ / _ \\ '_ \\ | / _ \\ / _\` |/ _ \\
|
|
31
|
+
| |_| | |_) | __/ | | | |__| (_) | (_| | __/
|
|
32
|
+
\\___/| .__/ \\___|_| |_|\\____\\___/ \\__,_|\\___|
|
|
33
|
+
|_| Azure Edition
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
// Get config path
|
|
37
|
+
function getConfigPath() {
|
|
38
|
+
const home = os.homedir();
|
|
39
|
+
return path.join(home, '.config', 'opencode', 'opencode.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Readline interface
|
|
43
|
+
const rl = readline.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function ask(question, defaultValue = '') {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
51
|
+
rl.question(prompt, (answer) => resolve(answer || defaultValue));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function askPassword(question) {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
process.stdout.write(`${question}: `);
|
|
58
|
+
|
|
59
|
+
if (process.stdin.isTTY) {
|
|
60
|
+
const stdin = process.stdin;
|
|
61
|
+
stdin.setRawMode(true);
|
|
62
|
+
stdin.resume();
|
|
63
|
+
stdin.setEncoding('utf8');
|
|
64
|
+
|
|
65
|
+
let password = '';
|
|
66
|
+
const onData = (char) => {
|
|
67
|
+
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
68
|
+
stdin.setRawMode(false);
|
|
69
|
+
stdin.removeListener('data', onData);
|
|
70
|
+
console.log();
|
|
71
|
+
resolve(password);
|
|
72
|
+
} else if (char === '\u0003') {
|
|
73
|
+
process.exit();
|
|
74
|
+
} else if (char === '\u007F' || char === '\b') {
|
|
75
|
+
password = password.slice(0, -1);
|
|
76
|
+
} else {
|
|
77
|
+
password += char;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
stdin.on('data', onData);
|
|
81
|
+
} else {
|
|
82
|
+
rl.question('', resolve);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
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`);
|
|
90
|
+
|
|
91
|
+
const options = {
|
|
92
|
+
hostname: url.hostname,
|
|
93
|
+
port: url.port || 443,
|
|
94
|
+
path: url.pathname + url.search,
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
'api-key': apiKey,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const body = JSON.stringify({
|
|
103
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
104
|
+
max_completion_tokens: 5,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const req = https.request(options, (res) => {
|
|
108
|
+
let data = '';
|
|
109
|
+
res.on('data', (chunk) => (data += chunk));
|
|
110
|
+
res.on('end', () => resolve({ ok: res.statusCode === 200, status: res.statusCode, body: data }));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
req.on('error', (e) => resolve({ ok: false, status: 0, body: e.message }));
|
|
114
|
+
req.setTimeout(10000, () => {
|
|
115
|
+
req.destroy();
|
|
116
|
+
resolve({ ok: false, status: 0, body: 'Timeout' });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
req.write(body);
|
|
120
|
+
req.end();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
console.log(colors.blue + logo + colors.reset);
|
|
126
|
+
console.log(colors.blue + 'Azure OpenAI Setup' + colors.reset);
|
|
127
|
+
console.log('─'.repeat(40));
|
|
128
|
+
console.log();
|
|
129
|
+
|
|
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');
|
|
134
|
+
|
|
135
|
+
if (!endpoint) {
|
|
136
|
+
console.log(colors.red + 'Endpoint is required' + colors.reset);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
endpoint = endpoint.replace(/\/$/, '');
|
|
141
|
+
if (!endpoint.endsWith('/openai')) endpoint += '/openai';
|
|
142
|
+
|
|
143
|
+
console.log();
|
|
144
|
+
|
|
145
|
+
// API Key
|
|
146
|
+
const apiKey = await askPassword('API Key');
|
|
147
|
+
if (!apiKey) {
|
|
148
|
+
console.log(colors.red + 'API Key is required' + colors.reset);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
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');
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(colors.blue + 'Testing connection...' + colors.reset);
|
|
161
|
+
|
|
162
|
+
const result = await testConnection(endpoint, apiKey, deployment);
|
|
163
|
+
|
|
164
|
+
if (result.ok) {
|
|
165
|
+
console.log(colors.green + '✓ Connection successful!' + colors.reset);
|
|
166
|
+
} else {
|
|
167
|
+
console.log(colors.red + `✗ Connection failed (${result.status || 'error'})` + colors.reset);
|
|
168
|
+
if (result.body) {
|
|
169
|
+
try {
|
|
170
|
+
const err = JSON.parse(result.body);
|
|
171
|
+
console.log(colors.dim + (err.error?.message || result.body.slice(0, 200)) + colors.reset);
|
|
172
|
+
} catch {
|
|
173
|
+
console.log(colors.dim + result.body.slice(0, 200) + colors.reset);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
console.log();
|
|
177
|
+
const cont = await ask('Continue anyway? (y/N)', 'N');
|
|
178
|
+
if (cont.toLowerCase() !== 'y') process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Create config
|
|
182
|
+
const configPath = getConfigPath();
|
|
183
|
+
const configDir = path.dirname(configPath);
|
|
184
|
+
|
|
185
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
186
|
+
|
|
187
|
+
const config = {
|
|
188
|
+
$schema: 'https://opencode.ai/config.json',
|
|
189
|
+
model: `azure/${deployment}`,
|
|
190
|
+
provider: {
|
|
191
|
+
azure: {
|
|
192
|
+
npm: '@ai-sdk/azure',
|
|
193
|
+
name: 'Azure OpenAI',
|
|
194
|
+
options: {
|
|
195
|
+
baseURL: endpoint,
|
|
196
|
+
apiKey: apiKey,
|
|
197
|
+
useDeploymentBasedUrls: true,
|
|
198
|
+
apiVersion: '2024-12-01-preview',
|
|
199
|
+
},
|
|
200
|
+
models: {
|
|
201
|
+
[deployment]: {
|
|
202
|
+
name: deployment,
|
|
203
|
+
limit: { context: 200000, output: 16384 },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
211
|
+
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(colors.green + `✓ Configuration saved to ${configPath}` + colors.reset);
|
|
214
|
+
console.log();
|
|
215
|
+
console.log(colors.blue + "You're all set! Run:" + colors.reset);
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(' opencode');
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(colors.dim + 'Tips:' + colors.reset);
|
|
220
|
+
console.log(' • View config: opencode azure status');
|
|
221
|
+
console.log(' • Reconfigure: opencode azure');
|
|
222
|
+
console.log();
|
|
223
|
+
|
|
224
|
+
rl.close();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main().catch((err) => {
|
|
228
|
+
console.error(colors.red + 'Error: ' + err.message + colors.reset);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-azure-setup",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Easy Azure OpenAI setup for OpenCode",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opencode-azure-setup": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"opencode",
|
|
15
|
+
"azure",
|
|
16
|
+
"openai",
|
|
17
|
+
"ai",
|
|
18
|
+
"cli",
|
|
19
|
+
"setup"
|
|
20
|
+
],
|
|
21
|
+
"author": "schwarztim",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/schwarztim/opencode.git"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
}
|
|
30
|
+
}
|