@monotykamary/pi-opencode-provider 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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@monotykamary/pi-opencode-provider",
3
+ "version": "1.0.3",
4
+ "description": "Opencode provider extension for pi - Access GPT, Claude, Gemini, GLM, MiniMax, Kimi and more through the opencode.ai API",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "keywords": [
8
+ "pi",
9
+ "extension",
10
+ "provider",
11
+ "opencode",
12
+ "ai",
13
+ "llm",
14
+ "gpt",
15
+ "claude",
16
+ "gemini"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "pi": {
24
+ "extensions": [
25
+ "./index.ts"
26
+ ]
27
+ },
28
+ "scripts": {
29
+ "clean": "echo 'nothing to clean'",
30
+ "build": "echo 'nothing to build'",
31
+ "check": "echo 'nothing to check'",
32
+ "update-models": "node scripts/update-models.js"
33
+ }
34
+ }
package/patch.json ADDED
@@ -0,0 +1,193 @@
1
+ {
2
+ "claude-opus-4-6": {
3
+ "compat": {
4
+ "forceAdaptiveThinking": true
5
+ },
6
+ "thinkingLevelMap": {
7
+ "xhigh": "max"
8
+ }
9
+ },
10
+ "claude-opus-4-7": {
11
+ "compat": {
12
+ "forceAdaptiveThinking": true
13
+ },
14
+ "thinkingLevelMap": {
15
+ "xhigh": "xhigh"
16
+ }
17
+ },
18
+ "claude-opus-4-8": {
19
+ "compat": {
20
+ "forceAdaptiveThinking": true
21
+ },
22
+ "thinkingLevelMap": {
23
+ "xhigh": "xhigh"
24
+ }
25
+ },
26
+ "deepseek-v4-flash-free": {
27
+ "compat": {
28
+ "requiresReasoningContentOnAssistantMessages": true,
29
+ "thinkingFormat": "deepseek"
30
+ },
31
+ "thinkingLevelMap": {
32
+ "minimal": null,
33
+ "low": null,
34
+ "medium": null,
35
+ "high": "high",
36
+ "xhigh": "max"
37
+ }
38
+ },
39
+ "gemini-3-flash": {
40
+ "thinkingLevelMap": {
41
+ "off": null
42
+ }
43
+ },
44
+ "gemini-3-pro": {
45
+ "thinkingLevelMap": {
46
+ "off": null
47
+ }
48
+ },
49
+ "gemini-3.1-pro": {
50
+ "thinkingLevelMap": {
51
+ "off": null,
52
+ "minimal": null,
53
+ "low": "LOW",
54
+ "medium": null,
55
+ "high": "HIGH"
56
+ }
57
+ },
58
+ "gemini-3.5-flash": {
59
+ "thinkingLevelMap": {
60
+ "off": null
61
+ }
62
+ },
63
+ "gpt-5": {
64
+ "thinkingLevelMap": {
65
+ "off": null
66
+ }
67
+ },
68
+ "gpt-5-codex": {
69
+ "thinkingLevelMap": {
70
+ "off": null
71
+ }
72
+ },
73
+ "gpt-5-nano": {
74
+ "thinkingLevelMap": {
75
+ "off": null
76
+ }
77
+ },
78
+ "gpt-5.1": {
79
+ "thinkingLevelMap": {
80
+ "off": null
81
+ }
82
+ },
83
+ "gpt-5.1-codex": {
84
+ "thinkingLevelMap": {
85
+ "off": null
86
+ }
87
+ },
88
+ "gpt-5.1-codex-max": {
89
+ "thinkingLevelMap": {
90
+ "off": null
91
+ }
92
+ },
93
+ "gpt-5.1-codex-mini": {
94
+ "thinkingLevelMap": {
95
+ "off": null
96
+ }
97
+ },
98
+ "gpt-5.2": {
99
+ "thinkingLevelMap": {
100
+ "off": null,
101
+ "xhigh": "xhigh"
102
+ }
103
+ },
104
+ "gpt-5.2-codex": {
105
+ "thinkingLevelMap": {
106
+ "off": null,
107
+ "xhigh": "xhigh"
108
+ }
109
+ },
110
+ "gpt-5.3-codex": {
111
+ "thinkingLevelMap": {
112
+ "off": null,
113
+ "xhigh": "xhigh"
114
+ }
115
+ },
116
+ "gpt-5.3-codex-spark": {
117
+ "thinkingLevelMap": {
118
+ "off": null,
119
+ "xhigh": "xhigh"
120
+ }
121
+ },
122
+ "gpt-5.4": {
123
+ "thinkingLevelMap": {
124
+ "off": null,
125
+ "xhigh": "xhigh"
126
+ }
127
+ },
128
+ "gpt-5.4-mini": {
129
+ "thinkingLevelMap": {
130
+ "off": null,
131
+ "xhigh": "xhigh"
132
+ }
133
+ },
134
+ "gpt-5.4-nano": {
135
+ "thinkingLevelMap": {
136
+ "off": null,
137
+ "xhigh": "xhigh"
138
+ }
139
+ },
140
+ "gpt-5.4-pro": {
141
+ "thinkingLevelMap": {
142
+ "off": null,
143
+ "xhigh": "xhigh"
144
+ }
145
+ },
146
+ "gpt-5.5": {
147
+ "thinkingLevelMap": {
148
+ "off": null,
149
+ "xhigh": "xhigh"
150
+ }
151
+ },
152
+ "gpt-5.5-pro": {
153
+ "thinkingLevelMap": {
154
+ "off": null,
155
+ "xhigh": "xhigh",
156
+ "minimal": null,
157
+ "low": null
158
+ }
159
+ },
160
+ "grok-build-0.1": {
161
+ "compat": {
162
+ "supportsReasoningEffort": false
163
+ },
164
+ "thinkingLevelMap": {
165
+ "off": null,
166
+ "minimal": null,
167
+ "low": null,
168
+ "medium": null
169
+ }
170
+ },
171
+ "grok-code": {
172
+ "compat": {
173
+ "supportsReasoningEffort": false
174
+ },
175
+ "thinkingLevelMap": {
176
+ "off": null,
177
+ "minimal": null,
178
+ "low": null,
179
+ "medium": null
180
+ }
181
+ },
182
+ "kimi-k2.6": {
183
+ "compat": {
184
+ "thinkingFormat": "deepseek",
185
+ "supportsReasoningEffort": false
186
+ },
187
+ "thinkingLevelMap": {
188
+ "minimal": null,
189
+ "low": null,
190
+ "medium": null
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script to update opencode models from models.dev API
5
+ * Writes to models.json (Pi-native format) and updates README.md.
6
+ *
7
+ * Pipeline: models.dev API → models.json (idempotent, re-runnable)
8
+ * patch.json + custom-models.json for our layered overrides
9
+ */
10
+
11
+ import https from 'https';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ const API_URL = 'https://models.dev/api.json';
20
+ const PROVIDER_ID = 'opencode';
21
+
22
+ // Map models.dev provider.npm to pi API type and base URL
23
+ const NPM_TO_API = {
24
+ '@ai-sdk/anthropic': { api: 'anthropic-messages', baseUrl: 'https://opencode.ai/zen' },
25
+ '@ai-sdk/openai': { api: 'openai-responses', baseUrl: 'https://opencode.ai/zen/v1' },
26
+ '@ai-sdk/google': { api: 'google-generative-ai', baseUrl: 'https://opencode.ai/zen/v1' },
27
+ };
28
+ const DEFAULT_API = { api: 'openai-completions', baseUrl: 'https://opencode.ai/zen/v1' };
29
+
30
+ // Fetch JSON from URL
31
+ function fetchJSON(url) {
32
+ return new Promise((resolve, reject) => {
33
+ https.get(url, (res) => {
34
+ let data = '';
35
+ res.on('data', chunk => data += chunk);
36
+ res.on('end', () => {
37
+ try {
38
+ resolve(JSON.parse(data));
39
+ } catch (e) {
40
+ reject(new Error(`Failed to parse JSON: ${e.message}`));
41
+ }
42
+ });
43
+ }).on('error', reject);
44
+ });
45
+ }
46
+
47
+ // Format cost for display
48
+ function formatCost(cost) {
49
+ if (cost === 0) return 'Free';
50
+ return `$${cost.toFixed(2)}`;
51
+ }
52
+
53
+ // Format number with K/M suffix
54
+ function formatNumber(num) {
55
+ if (num === null || num === undefined) return '-';
56
+ if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
57
+ if (num >= 1000) return `${(num / 1000).toFixed(0)}K`;
58
+ return num.toString();
59
+ }
60
+
61
+ // Get input types from modalities (pi supports "text" and "image" only)
62
+ function getInputTypes(modalities) {
63
+ const raw = modalities?.input || ['text'];
64
+ const filtered = raw.filter(m => m === 'text' || m === 'image');
65
+ if (!filtered.includes('text')) filtered.unshift('text');
66
+ return filtered;
67
+ }
68
+
69
+ // Get API label for display
70
+ function getApiLabel(api) {
71
+ const labels = {
72
+ 'anthropic-messages': 'Anthropic',
73
+ 'openai-responses': 'Responses',
74
+ 'openai-completions': 'Completions',
75
+ 'google-generative-ai': 'Gemini',
76
+ };
77
+ return labels[api] || api;
78
+ }
79
+
80
+ // Convert API model to Pi-native format with per-model api/baseUrl
81
+ function convertModel(model) {
82
+ const npm = model.provider?.npm;
83
+ const { api, baseUrl } = (npm && NPM_TO_API[npm]) || DEFAULT_API;
84
+ const inputTypes = getInputTypes(model.modalities);
85
+ const cost = model.cost || {};
86
+ const limit = model.limit || {};
87
+
88
+ return {
89
+ id: model.id,
90
+ name: model.name,
91
+ api,
92
+ baseUrl,
93
+ reasoning: model.reasoning || false,
94
+ input: inputTypes,
95
+ cost: {
96
+ input: cost.input || 0,
97
+ output: cost.output || 0,
98
+ cacheRead: cost.cache_read || 0,
99
+ cacheWrite: cost.cache_write || 0,
100
+ },
101
+ contextWindow: limit.context || 0,
102
+ maxTokens: limit.output || 0,
103
+ };
104
+ }
105
+
106
+ // Generate README model table row
107
+ function generateReadmeRow(model) {
108
+ const cost = model.cost || {};
109
+ const hasImage = model.input.includes('image');
110
+ const typeLabel = hasImage ? 'Text + Image' : 'Text';
111
+ return `| ${model.name} | ${getApiLabel(model.api)} | ${typeLabel} | ${formatNumber(model.contextWindow)} | ${formatNumber(model.maxTokens)} | ${formatCost(cost.input)} | ${formatCost(cost.output)} |`;
112
+ }
113
+
114
+ // Update README model table
115
+ function updateReadme(models) {
116
+ const readmePath = path.join(process.cwd(), 'README.md');
117
+ let readme;
118
+
119
+ try {
120
+ readme = fs.readFileSync(readmePath, 'utf8');
121
+ } catch {
122
+ console.log(' No README.md found, skipping README update');
123
+ return;
124
+ }
125
+
126
+ // Sort models by name
127
+ const sortedModels = [...models].sort((a, b) => a.name.localeCompare(b.name));
128
+
129
+ // Generate table rows
130
+ const tableRows = sortedModels.map(generateReadmeRow).join('\n');
131
+ const newTable = `| Model | API | Type | Context | Max Tokens | Input Cost | Output Cost |
132
+ |-------|-----|------|---------|------------|------------|-------------|
133
+ ${tableRows}`;
134
+
135
+ // Replace table in README
136
+ const tableRegex = /\| Model \| API \| Type \| Context \| Max Tokens \| Input Cost \| Output Cost \|[\s\S]*?(?=\n\*Costs are per million)/;
137
+ if (readme.match(tableRegex)) {
138
+ readme = readme.replace(tableRegex, newTable);
139
+ } else {
140
+ // Fallback: replace old 6-column table format
141
+ const oldTableRegex = /\| Model \| Type \| Context \| Max Tokens \| Input Cost \| Output Cost \|[\s\S]*?(?=\n\*Costs are per million)/;
142
+ if (readme.match(oldTableRegex)) {
143
+ readme = readme.replace(oldTableRegex, newTable);
144
+ }
145
+ }
146
+
147
+ // Update model count in features
148
+ readme = readme.replace(/\*\*\d+\+ AI Models\*\*/, `**${models.length}+ AI Models**`);
149
+
150
+ fs.writeFileSync(readmePath, readme);
151
+ console.log(` Updated README.md with ${models.length} models`);
152
+ }
153
+
154
+ async function main() {
155
+ console.log('Fetching models from API...');
156
+
157
+ try {
158
+ const data = await fetchJSON(API_URL);
159
+ const provider = data[PROVIDER_ID];
160
+
161
+ if (!provider) {
162
+ throw new Error(`Provider "${PROVIDER_ID}" not found in API`);
163
+ }
164
+
165
+ if (!provider.models) {
166
+ throw new Error(`No models found for provider "${PROVIDER_ID}"`);
167
+ }
168
+
169
+ // Convert models object to array and filter out deprecated
170
+ const apiModels = Object.values(provider.models).filter(m => m.status !== 'deprecated');
171
+
172
+ console.log(`Found ${apiModels.length} active models`);
173
+
174
+ // Convert to Pi-native format and save to models.json
175
+ const models = apiModels.map(convertModel);
176
+ const modelsPath = path.join(process.cwd(), 'models.json');
177
+ fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2) + '\n');
178
+ console.log(` Saved ${models.length} models to models.json`);
179
+
180
+ // Update README
181
+ updateReadme(models);
182
+
183
+ console.log('\nDone!');
184
+ } catch (error) {
185
+ console.error('Error:', error.message);
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ main();