@crazy-goat/nexos-provider 1.3.0 → 1.3.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.
- package/README.md +24 -0
- package/fix-chatgpt.mjs +11 -0
- package/fix-claude.mjs +38 -0
- package/fix-gemini.mjs +109 -0
- package/package.json +4 -6
- package/list-models.mjs +0 -47
package/README.md
CHANGED
|
@@ -105,6 +105,30 @@ opencode → createNexosAI → fetch wrapper → nexos.ai API
|
|
|
105
105
|
└─ fix-chatgpt.mjs: passthrough (no fixes needed)
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## Testing
|
|
109
|
+
|
|
110
|
+
Test with a simple prompt:
|
|
111
|
+
```bash
|
|
112
|
+
opencode run "what is 2+2?" -m "nexos-ai/Gemini 2.5 Pro"
|
|
113
|
+
opencode run "what is 2+2?" -m "nexos-ai/Gemini 2.5 Flash"
|
|
114
|
+
opencode run "what is 2+2?" -m "nexos-ai/Claude Sonnet 4.5"
|
|
115
|
+
opencode run "what is 2+2?" -m "nexos-ai/GPT 5"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Test tool calling:
|
|
119
|
+
```bash
|
|
120
|
+
opencode run "list files in current directory" -m "nexos-ai/Gemini 2.5 Pro"
|
|
121
|
+
opencode run "list files in current directory" -m "nexos-ai/Claude Sonnet 4.5"
|
|
122
|
+
opencode run "list files in current directory" -m "nexos-ai/GPT 5"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Test thinking/reasoning variants:
|
|
126
|
+
```bash
|
|
127
|
+
opencode run "what is 2+2?" -m "nexos-ai/Claude Sonnet 4.5" --variant thinking-high
|
|
128
|
+
opencode run "what is 2+2?" -m "nexos-ai/Gemini 2.5 Pro" --variant thinking-high
|
|
129
|
+
opencode run "what is 2+2?" -m "nexos-ai/GPT 5" --variant high
|
|
130
|
+
```
|
|
131
|
+
|
|
108
132
|
## License
|
|
109
133
|
|
|
110
134
|
MIT
|
package/fix-chatgpt.mjs
ADDED
package/fix-claude.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function fixClaudeRequest(body) {
|
|
2
|
+
if (!body.thinking) return { body, hadThinking: false };
|
|
3
|
+
if (body.thinking.type === "disabled") {
|
|
4
|
+
const { thinking, ...rest } = body;
|
|
5
|
+
return { body: rest, hadThinking: true };
|
|
6
|
+
}
|
|
7
|
+
const thinking = { ...body.thinking };
|
|
8
|
+
if (thinking.budgetTokens !== undefined && thinking.budget_tokens === undefined) {
|
|
9
|
+
thinking.budget_tokens = thinking.budgetTokens;
|
|
10
|
+
delete thinking.budgetTokens;
|
|
11
|
+
}
|
|
12
|
+
const result = { ...body, thinking };
|
|
13
|
+
if (thinking.budget_tokens && result.max_tokens && result.max_tokens <= thinking.budget_tokens) {
|
|
14
|
+
result.max_tokens = thinking.budget_tokens + 4096;
|
|
15
|
+
}
|
|
16
|
+
return { body: result, hadThinking: true };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function fixClaudeStream(text) {
|
|
20
|
+
return text.replace(/data: ({.*})\n/g, (match, jsonStr) => {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(jsonStr);
|
|
23
|
+
let changed = false;
|
|
24
|
+
if (parsed.choices) {
|
|
25
|
+
for (const choice of parsed.choices) {
|
|
26
|
+
if (choice.finish_reason === "end_turn") {
|
|
27
|
+
choice.finish_reason = "stop";
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (changed) {
|
|
33
|
+
return "data: " + JSON.stringify(parsed) + "\n";
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
return match;
|
|
37
|
+
});
|
|
38
|
+
}
|
package/fix-gemini.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
function resolveRefs(schema, defs) {
|
|
2
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
3
|
+
if (Array.isArray(schema)) return schema.map((s) => resolveRefs(s, defs));
|
|
4
|
+
|
|
5
|
+
if (schema.$ref || schema.ref) {
|
|
6
|
+
const refName = (schema.$ref || schema.ref)
|
|
7
|
+
.replace(/^#\/\$defs\//, "")
|
|
8
|
+
.replace(/^#\/definitions\//, "");
|
|
9
|
+
const resolved = defs?.[refName];
|
|
10
|
+
if (resolved) {
|
|
11
|
+
const merged = { ...resolveRefs(resolved, defs) };
|
|
12
|
+
if (schema.description) merged.description = schema.description;
|
|
13
|
+
if (schema.default !== undefined) merged.default = schema.default;
|
|
14
|
+
return merged;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [k, v] of Object.entries(schema)) {
|
|
20
|
+
if (k === "$defs" || k === "definitions" || k === "$ref" || k === "ref")
|
|
21
|
+
continue;
|
|
22
|
+
result[k] = resolveRefs(v, defs);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fixToolSchemas(body) {
|
|
28
|
+
if (!body.tools?.length) return body;
|
|
29
|
+
return {
|
|
30
|
+
...body,
|
|
31
|
+
tools: body.tools.map((tool) => {
|
|
32
|
+
if (tool.type !== "function" || !tool.function?.parameters) return tool;
|
|
33
|
+
const params = tool.function.parameters;
|
|
34
|
+
const defs = params.$defs || params.definitions || {};
|
|
35
|
+
return {
|
|
36
|
+
...tool,
|
|
37
|
+
function: {
|
|
38
|
+
...tool.function,
|
|
39
|
+
parameters: resolveRefs(params, defs),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isGeminiModel(model) {
|
|
47
|
+
return typeof model === "string" && model.toLowerCase().includes("gemini");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function fixGeminiRequest(body) {
|
|
51
|
+
if (body.tools) {
|
|
52
|
+
body = fixToolSchemas(body);
|
|
53
|
+
}
|
|
54
|
+
return body;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function fixGeminiThinkingRequest(body) {
|
|
58
|
+
if (!body.thinking) return { body, hadThinking: false };
|
|
59
|
+
if (body.thinking.type === "disabled") {
|
|
60
|
+
const { thinking, ...rest } = body;
|
|
61
|
+
return { body: rest, hadThinking: true };
|
|
62
|
+
}
|
|
63
|
+
const thinking = { ...body.thinking };
|
|
64
|
+
if (thinking.budgetTokens !== undefined && thinking.budget_tokens === undefined) {
|
|
65
|
+
thinking.budget_tokens = thinking.budgetTokens;
|
|
66
|
+
delete thinking.budgetTokens;
|
|
67
|
+
}
|
|
68
|
+
const result = { ...body, thinking };
|
|
69
|
+
if (thinking.budget_tokens && result.max_tokens && result.max_tokens <= thinking.budget_tokens) {
|
|
70
|
+
result.max_tokens = thinking.budget_tokens + 4096;
|
|
71
|
+
}
|
|
72
|
+
return { body: result, hadThinking: true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function fixGeminiStream(text) {
|
|
76
|
+
return text.replace(/data: ({.*})\n/g, (match, jsonStr) => {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(jsonStr);
|
|
79
|
+
let changed = false;
|
|
80
|
+
if (parsed.choices) {
|
|
81
|
+
for (const choice of parsed.choices) {
|
|
82
|
+
if (choice.finish_reason === "STOP") {
|
|
83
|
+
choice.finish_reason = "stop";
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
if (choice.finish_reason === "stop" && choice.delta?.tool_calls?.length) {
|
|
87
|
+
choice.finish_reason = "tool_calls";
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
const blocks = choice.delta?.content_blocks;
|
|
91
|
+
if (blocks?.length) {
|
|
92
|
+
for (const block of blocks) {
|
|
93
|
+
if (block.delta?.thinking) {
|
|
94
|
+
choice.delta.reasoning_content = block.delta.thinking;
|
|
95
|
+
changed = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
delete choice.delta.content_blocks;
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (changed) {
|
|
104
|
+
return "data: " + JSON.stringify(parsed) + "\n";
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
return match;
|
|
108
|
+
});
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crazy-goat/nexos-provider",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Custom AI SDK provider for nexos.ai Gemini models in opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.mjs"
|
|
9
9
|
},
|
|
10
|
-
"bin": "./list-models.mjs",
|
|
11
|
-
"scripts": {
|
|
12
|
-
"list-models": "node list-models.mjs"
|
|
13
|
-
},
|
|
14
10
|
"files": [
|
|
15
11
|
"index.mjs",
|
|
16
|
-
"
|
|
12
|
+
"fix-gemini.mjs",
|
|
13
|
+
"fix-claude.mjs",
|
|
14
|
+
"fix-chatgpt.mjs"
|
|
17
15
|
],
|
|
18
16
|
"keywords": [
|
|
19
17
|
"opencode",
|
package/list-models.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const baseURL = process.env.NEXOS_BASE_URL || "https://api.nexos.ai/v1";
|
|
4
|
-
const apiKey = process.env.NEXOS_API_KEY;
|
|
5
|
-
|
|
6
|
-
if (!apiKey) {
|
|
7
|
-
console.error("Error: NEXOS_API_KEY environment variable is not set");
|
|
8
|
-
process.exit(1);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const res = await fetch(`${baseURL}/models`, {
|
|
12
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
if (!res.ok) {
|
|
16
|
-
console.error(`Error: ${res.status} ${res.statusText}`);
|
|
17
|
-
const body = await res.text();
|
|
18
|
-
if (body) console.error(body);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const data = await res.json();
|
|
23
|
-
const models = (data.data || data.models || []).sort((a, b) =>
|
|
24
|
-
(a.id || a.name || "").localeCompare(b.id || b.name || "")
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
if (models.length === 0) {
|
|
28
|
-
console.log("No models found.");
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(`\nAvailable models (${models.length}):\n`);
|
|
33
|
-
|
|
34
|
-
for (const m of models) {
|
|
35
|
-
const id = m.id || m.name;
|
|
36
|
-
console.log(` - ${id}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log("\nTo use a model in opencode.json, add it under provider.models:");
|
|
40
|
-
console.log(`
|
|
41
|
-
"models": {
|
|
42
|
-
"<model-id>": {
|
|
43
|
-
"name": "<model-id>",
|
|
44
|
-
"limit": { "context": 128000, "output": 64000 }
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
`);
|