opencode-plugin-tsaperture 0.1.1 → 0.1.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/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "opencode-plugin-tsaperture",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "OpenCode plugin to automatically populate models from Tailscale Aperture API",
5
5
  "homepage": "https://github.com/SharkyRawr/opencode-plugin-tsaperture",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
9
12
  "keywords": [
10
13
  "opencode",
11
14
  "plugin",
@@ -15,7 +18,7 @@
15
18
  "author": "",
16
19
  "license": "MIT",
17
20
  "dependencies": {
18
- "@opencode-ai/plugin": "^1.3.10"
21
+ "@opencode-ai/plugin": "^1.3.11"
19
22
  },
20
23
  "devDependencies": {
21
24
  "typescript": "^5.9.3",
package/src/index.ts DELETED
@@ -1,356 +0,0 @@
1
- import type { Plugin } from "@opencode-ai/plugin";
2
- import { tool } from "@opencode-ai/plugin";
3
- import { homedir } from "os";
4
- import { join } from "path";
5
- import { readFile } from "fs/promises";
6
- import { platform } from "process";
7
-
8
- interface ApertureModel {
9
- id: string;
10
- object: string;
11
- created: number;
12
- owned_by: string;
13
- metadata?: {
14
- provider?: {
15
- id: string;
16
- name: string;
17
- description?: string;
18
- };
19
- };
20
- }
21
-
22
- interface ApertureResponse {
23
- object: string;
24
- data: ApertureModel[];
25
- }
26
-
27
- interface ApertureConfig {
28
- baseUrl?: string;
29
- apiKey?: string;
30
- }
31
-
32
- type ModelConfig = {
33
- id: string;
34
- name: string;
35
- limit: {
36
- context: number;
37
- output: number;
38
- };
39
- reasoning: boolean;
40
- temperature: boolean;
41
- tool_call: boolean;
42
- modalities: {
43
- input: Array<"text">;
44
- output: Array<"text">;
45
- };
46
- interleaved?: true | {
47
- field: "reasoning_content" | "reasoning_details";
48
- };
49
- options?: {
50
- thinking?: {
51
- type?: string;
52
- clear_thinking?: boolean;
53
- };
54
- };
55
- headers?: Record<string, string>;
56
- };
57
-
58
- function normalizeBaseUrl(baseUrl: string): string {
59
- return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
60
- }
61
-
62
- function getModelDefaults(model: ApertureModel): Omit<ModelConfig, "id" | "name"> {
63
- const id = model.id.toLowerCase();
64
- const providerID = model.metadata?.provider?.id?.toLowerCase();
65
- const providerName = model.metadata?.provider?.name?.toLowerCase();
66
- const isZai = id.includes("glm")
67
- || providerID === "zai"
68
- || providerID === "z.ai"
69
- || providerID === "zai-coding-plan"
70
- || providerName === "z.ai"
71
- || providerName === "zai-coding-plan";
72
- const isKimi = id.includes("kimi")
73
- || providerID === "kimi"
74
- || providerID === "kimi-for-coding"
75
- || providerName === "kimi"
76
- || providerName === "kimi-for-coding";
77
-
78
- if (isZai) {
79
- return {
80
- limit: {
81
- context: 200_000,
82
- output: 8_192,
83
- },
84
- reasoning: true,
85
- temperature: true,
86
- tool_call: true,
87
- modalities: {
88
- input: ["text"],
89
- output: ["text"],
90
- },
91
- interleaved: {
92
- field: "reasoning_content",
93
- },
94
- options: {
95
- thinking: {
96
- type: "enabled",
97
- clear_thinking: false,
98
- },
99
- },
100
- };
101
- }
102
-
103
- if (isKimi) {
104
- return {
105
- limit: {
106
- context: 200_000,
107
- output: 128_000,
108
- },
109
- reasoning: true,
110
- temperature: true,
111
- tool_call: true,
112
- modalities: {
113
- input: ["text"],
114
- output: ["text"],
115
- },
116
- interleaved: {
117
- field: "reasoning_content",
118
- },
119
- options: {
120
- thinking: {
121
- type: "enabled",
122
- clear_thinking: false,
123
- },
124
- },
125
- headers: {
126
- "User-Agent": "KimiCLI/1.3",
127
- },
128
- };
129
- }
130
-
131
- return {
132
- limit: {
133
- context: 128_000,
134
- output: 8_192,
135
- },
136
- reasoning: false,
137
- temperature: true,
138
- tool_call: true,
139
- modalities: {
140
- input: ["text"],
141
- output: ["text"],
142
- },
143
- interleaved: {
144
- field: "reasoning_content",
145
- },
146
- options: {
147
- thinking: {
148
- type: "enabled",
149
- clear_thinking: false,
150
- },
151
- },
152
- };
153
- }
154
-
155
- async function fetchApertureModels(baseUrl: string): Promise<ApertureModel[]> {
156
- const response = await fetch(`${baseUrl}/v1/models`);
157
- if (!response.ok) {
158
- throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
159
- }
160
-
161
- const data = await response.json() as ApertureResponse;
162
- return data.data || [];
163
- }
164
-
165
- function getOpenCodeConfigDirs(): string[] {
166
- const home = homedir();
167
- const dirs: string[] = [];
168
-
169
- if (platform === "win32") {
170
- dirs.push(join(process.env.APPDATA || process.env.LOCALAPPDATA || home, "opencode"));
171
- } else if (platform === "darwin") {
172
- const xdgConfig = process.env.XDG_CONFIG_HOME;
173
- if (xdgConfig) {
174
- dirs.push(join(xdgConfig, "opencode"));
175
- }
176
- dirs.push(join(home, ".config", "opencode"));
177
- dirs.push(join(home, "Library", "Application Support", "opencode"));
178
- } else {
179
- const xdgConfig = process.env.XDG_CONFIG_HOME;
180
- if (xdgConfig) {
181
- dirs.push(join(xdgConfig, "opencode"));
182
- }
183
- dirs.push(join(home, ".config", "opencode"));
184
- }
185
-
186
- return dirs;
187
- }
188
-
189
- const openCodeConfigDirs = getOpenCodeConfigDirs();
190
-
191
- async function loadApertureConfig(): Promise<ApertureConfig> {
192
- for (const configDir of openCodeConfigDirs) {
193
- const configPath = join(configDir, "aperture.json");
194
- try {
195
- const content = await readFile(configPath, "utf-8");
196
- console.log(`[TailscaleAperture] Loaded config from ${configPath}`);
197
- return JSON.parse(content) as ApertureConfig;
198
- } catch (error) {
199
- if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
200
- console.warn(`[TailscaleAperture] Failed to read ${configPath}:`, error);
201
- }
202
- }
203
- }
204
-
205
- return {};
206
- }
207
-
208
- export const TailscaleAperturePlugin: Plugin = async (_ctx, options) => {
209
- const fileConfig = await loadApertureConfig();
210
- const rawBaseUrl = (options?.baseUrl as string) || process.env.APERTURE_BASE_URL || fileConfig.baseUrl;
211
- const apiKey = (options?.apiKey as string) || process.env.APERTURE_API_KEY || fileConfig.apiKey || "";
212
-
213
- if (!rawBaseUrl) {
214
- console.warn("[TailscaleAperture] No baseUrl configured. Set APERTURE_BASE_URL, add baseUrl to plugin options, or create aperture.json in opencode config directory.");
215
- return {};
216
- }
217
-
218
- const baseUrl = normalizeBaseUrl(rawBaseUrl);
219
- let discoveredModels: ApertureModel[] = [];
220
- let modelsLoaded = false;
221
-
222
- async function loadModels(refresh = false): Promise<ApertureModel[]> {
223
- if (!refresh && modelsLoaded) {
224
- return discoveredModels;
225
- }
226
-
227
- discoveredModels = await fetchApertureModels(baseUrl);
228
- modelsLoaded = true;
229
- return discoveredModels;
230
- }
231
-
232
- try {
233
- discoveredModels = await loadModels(true);
234
- if (discoveredModels.length === 0) {
235
- console.warn("[TailscaleAperture] No models found");
236
- } else {
237
- console.log(`[TailscaleAperture] Discovered ${discoveredModels.length} models from ${baseUrl}`);
238
- }
239
- } catch (error) {
240
- console.warn("[TailscaleAperture] Failed to preload models:", error);
241
- }
242
-
243
- return {
244
- config: async (config: any) => {
245
- try {
246
- config.provider ??= {};
247
-
248
- if (discoveredModels.length === 0) {
249
- return;
250
- }
251
-
252
- const existingProvider = config.provider.aperture ?? {};
253
- config.provider.aperture = {
254
- ...existingProvider,
255
- npm: existingProvider.npm ?? "@ai-sdk/openai-compatible",
256
- name: existingProvider.name ?? "Tailscale Aperture",
257
- options: {
258
- ...existingProvider.options,
259
- baseURL: `${baseUrl}/v1`,
260
- apiKey: existingProvider.options?.apiKey ?? apiKey,
261
- },
262
- models: {
263
- ...existingProvider.models,
264
- },
265
- };
266
-
267
- // Add discovered models while preserving explicit user overrides.
268
- for (const model of discoveredModels) {
269
- const existingModel = config.provider.aperture.models[model.id] ?? {};
270
- const defaults = getModelDefaults(model);
271
- config.provider.aperture.models[model.id] = {
272
- ...defaults,
273
- ...existingModel,
274
- limit: {
275
- ...defaults.limit,
276
- ...existingModel.limit,
277
- },
278
- modalities: {
279
- ...defaults.modalities,
280
- ...existingModel.modalities,
281
- },
282
- ...(defaults.interleaved || existingModel.interleaved ? {
283
- interleaved: existingModel.interleaved ?? defaults.interleaved,
284
- } : {}),
285
- ...(defaults.options || existingModel.options ? {
286
- options: {
287
- ...defaults.options,
288
- ...existingModel.options,
289
- ...(defaults.options?.thinking || existingModel.options?.thinking ? {
290
- thinking: {
291
- ...defaults.options?.thinking,
292
- ...existingModel.options?.thinking,
293
- },
294
- } : {}),
295
- },
296
- } : {}),
297
- ...(defaults.headers || existingModel.headers ? {
298
- headers: {
299
- ...defaults.headers,
300
- ...existingModel.headers,
301
- },
302
- } : {}),
303
- id: model.id,
304
- name: existingModel.name ?? model.id,
305
- };
306
- }
307
-
308
- console.log(`[TailscaleAperture] Registered ${discoveredModels.length} models`);
309
- } catch (error) {
310
- console.error("[TailscaleAperture] Failed to register models:", error);
311
- }
312
- },
313
-
314
- tool: {
315
- list_aperture_models: tool({
316
- description: "List available models from Tailscale Aperture",
317
- args: {
318
- refresh: tool.schema.boolean().optional().describe("Refresh the cached Aperture model list before returning it"),
319
- },
320
- async execute(args) {
321
- try {
322
- const models = await loadModels(args.refresh ?? false);
323
- return JSON.stringify({
324
- models,
325
- count: models.length,
326
- }, null, 2);
327
- } catch (error) {
328
- return JSON.stringify({ error: String(error) });
329
- }
330
- },
331
- }),
332
-
333
- get_aperture_model: tool({
334
- description: "Get details for a specific Aperture model",
335
- args: {
336
- modelId: tool.schema.string().describe("Model ID"),
337
- refresh: tool.schema.boolean().optional().describe("Refresh the cached Aperture model list before looking up the model"),
338
- },
339
- async execute(args) {
340
- try {
341
- const models = await loadModels(args.refresh ?? false);
342
- const model = models.find(m => m.id === args.modelId);
343
- if (!model) {
344
- return JSON.stringify({ error: `Model ${args.modelId} not found` });
345
- }
346
- return JSON.stringify({ model }, null, 2);
347
- } catch (error) {
348
- return JSON.stringify({ error: String(error) });
349
- }
350
- },
351
- }),
352
- },
353
- };
354
- };
355
-
356
- export default TailscaleAperturePlugin;
package/tsconfig.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true,
16
- "resolveJsonModule": true,
17
- "noImplicitAny": true,
18
- "strictNullChecks": true,
19
- "strictFunctionTypes": true,
20
- "strictBindCallApply": true,
21
- "strictPropertyInitialization": true,
22
- "noImplicitThis": true,
23
- "alwaysStrict": true
24
- },
25
- "include": ["src/**/*"],
26
- "exclude": ["node_modules", "dist"]
27
- }