@siduri-x/brain 2.0.2 → 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 +29 -4
- package/dist/index.test.js +25 -0
- package/dist/prompt.js +6 -0
- package/organ-manifest.json +1 -1
- package/package.json +2 -2
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}): ${
|
|
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: ${
|
|
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) {
|
package/dist/index.test.js
CHANGED
|
@@ -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 () => {
|
package/dist/prompt.js
CHANGED
|
@@ -22,6 +22,12 @@ class PromptAssembler {
|
|
|
22
22
|
"[CONTEXTUAL AWARENESS]",
|
|
23
23
|
context.contextPrompt,
|
|
24
24
|
"[RESPONSE RULES] Use confirmed permitted memories as factual context with their provenance. Return one semantic response containing your speech, internal monologue, and any memory or behavior proposals.",
|
|
25
|
+
"[COGNITIVE PROPOSAL INSTRUCTIONS]",
|
|
26
|
+
"You are the primary cognitive proposer for the companion's living memory and self.",
|
|
27
|
+
"When the user shares personal facts, names, affiliations, relationship declarations, preferences, or behavioral instructions (or when in Teach Mode):",
|
|
28
|
+
"- Propose factual claims in `memoryProposals` with subject ('actor:<id>' for user facts or 'companion:<id>' for companion facts), predicate (e.g. 'name', 'role', 'affiliation', 'origin', 'stated_relationship'), and value.",
|
|
29
|
+
"- Propose directives in `behaviorProposals` with directive (e.g. 'Address actor:<id> as <name>', 'Acknowledge role as <role>'), and category ('relational' | 'behavioral' | 'guardrail').",
|
|
30
|
+
"All proposals will enter pending status for owner review before taking effect.",
|
|
25
31
|
];
|
|
26
32
|
return promptParts.join("\n");
|
|
27
33
|
}
|
package/organ-manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siduri-x/brain",
|
|
3
3
|
"organType": "brain",
|
|
4
|
-
"version": "2.0.
|
|
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,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siduri-x/brain",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"zod": "^4.6.2",
|
|
8
|
-
"@siduri-x/core": "2.0.
|
|
8
|
+
"@siduri-x/core": "2.0.6"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@types/jest": "^30.0.0",
|