@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 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
@@ -0,0 +1,11 @@
1
+ export function fixChatGPTRequest(body) {
2
+ if (body.reasoning_effort === "none") {
3
+ const { reasoning_effort, ...rest } = body;
4
+ return rest;
5
+ }
6
+ return body;
7
+ }
8
+
9
+ export function fixChatGPTStream(text) {
10
+ return text;
11
+ }
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.0",
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
- "list-models.mjs"
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
- `);