@siduri-x/brain 2.0.3 → 2.0.4

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/dist/index.js CHANGED
@@ -156,6 +156,23 @@ class OpenAICompatibleBrain {
156
156
  });
157
157
  if (!response.ok) {
158
158
  const status = response.status;
159
+ let errorDetails = response.statusText || `HTTP ${status}`;
160
+ try {
161
+ if (typeof response.text === 'function') {
162
+ const errorText = await response.text();
163
+ try {
164
+ const parsed = JSON.parse(errorText);
165
+ errorDetails = parsed?.error?.message || parsed?.message || errorText;
166
+ }
167
+ catch {
168
+ if (errorText)
169
+ errorDetails = errorText;
170
+ }
171
+ }
172
+ }
173
+ catch {
174
+ // fallback
175
+ }
159
176
  const retryHeader = response.headers?.get ? response.headers.get('retry-after') : undefined;
160
177
  if (retryHeader) {
161
178
  const parsedSec = parseInt(retryHeader, 10);
@@ -163,11 +180,11 @@ class OpenAICompatibleBrain {
163
180
  retryAfterSec = parsedSec;
164
181
  }
165
182
  }
166
- // Client authentication, forbidden, and bad request errors are fatal and should not be retried
167
- if (status === 400 || status === 401 || status === 403 || status === 404) {
168
- throw new Error(`Fatal upstream API error (${status}): ${response.statusText}`);
183
+ // Client authentication, forbidden, insufficient credits, and bad request errors are fatal and should not be retried
184
+ if (status === 400 || status === 401 || status === 402 || status === 403 || status === 404) {
185
+ throw new Error(`Fatal upstream API error (${status}): ${errorDetails}`);
169
186
  }
170
- throw new Error(`OpenRouter API error: ${response.statusText}`);
187
+ throw new Error(`OpenRouter API error (${status}): ${errorDetails}`);
171
188
  }
172
189
  const data = await response.json();
173
190
  const toolCall = data.choices?.[0]?.message?.tool_calls?.[0];
@@ -176,6 +193,14 @@ class OpenAICompatibleBrain {
176
193
  const parsed = ResponsePlanSchema.parse(rawArgs);
177
194
  return parsed;
178
195
  }
196
+ const directContent = data.choices?.[0]?.message?.content;
197
+ if (typeof directContent === 'string' && directContent.trim().length > 0) {
198
+ return {
199
+ speech: directContent.trim(),
200
+ language: 'en',
201
+ internalMonologue: 'Direct completion without tool call',
202
+ };
203
+ }
179
204
  throw new Error("No valid tool call returned from OpenRouter");
180
205
  }
181
206
  catch (e) {
@@ -119,6 +119,31 @@ describe('OpenRouterBrain', () => {
119
119
  });
120
120
  await expect(fastTimeoutBrain.generatePlan(mockContext)).rejects.toThrow(/deadline/i);
121
121
  });
122
+ test('extracts detailed provider error message from upstream JSON body', async () => {
123
+ global.fetch.mockResolvedValue({
124
+ ok: false,
125
+ status: 402,
126
+ statusText: "Payment Required",
127
+ text: async () => JSON.stringify({ error: { message: "Provider balance exhausted. Please top up your account." } }),
128
+ });
129
+ await expect(brain.generatePlan(mockContext)).rejects.toThrow("Provider balance exhausted");
130
+ expect(global.fetch).toHaveBeenCalledTimes(1);
131
+ });
132
+ test('falls back gracefully to direct message content when model omits tool_calls', async () => {
133
+ global.fetch.mockResolvedValueOnce({
134
+ ok: true,
135
+ json: async () => ({
136
+ choices: [{
137
+ message: {
138
+ content: "Hello from direct completion!",
139
+ },
140
+ }],
141
+ }),
142
+ });
143
+ const plan = await brain.generatePlan(mockContext);
144
+ expect(plan.speech).toBe("Hello from direct completion!");
145
+ expect(plan.language).toBe("en");
146
+ });
122
147
  });
123
148
  describe('OpenAICompatibleBrain', () => {
124
149
  test('uses a configurable OpenAI-compatible endpoint', async () => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@siduri-x/brain",
3
3
  "organType": "brain",
4
- "version": "2.0.2",
4
+ "version": "2.0.4",
5
5
  "displayName": "Brain (Cognition & Planning)",
6
6
  "description": "Provider-neutral LLM reasoning, response planning, and proposal generation",
7
7
  "entrypoint": "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/brain",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {