rhachet-brains-openai 0.2.0 → 0.2.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/dist/index.js
CHANGED
|
@@ -39,8 +39,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
39
39
|
|
|
40
40
|
// src/domain.operations/atoms/genBrainAtom.ts
|
|
41
41
|
var import_openai = __toESM(require("openai"));
|
|
42
|
-
var
|
|
43
|
-
var import_calcBrainOutputCost = require("rhachet/dist/domain.operations/brainCost/calcBrainOutputCost");
|
|
42
|
+
var import_brains = require("rhachet/brains");
|
|
44
43
|
var import_zod = require("zod");
|
|
45
44
|
|
|
46
45
|
// src/domain.objects/BrainAtom.config.ts
|
|
@@ -806,7 +805,7 @@ var CONFIG_BY_ATOM_SLUG = {
|
|
|
806
805
|
// src/domain.operations/atoms/genBrainAtom.ts
|
|
807
806
|
var genBrainAtom = (input) => {
|
|
808
807
|
const config = CONFIG_BY_ATOM_SLUG[input.slug];
|
|
809
|
-
return new
|
|
808
|
+
return new import_brains.BrainAtom({
|
|
810
809
|
repo: "openai",
|
|
811
810
|
slug: input.slug,
|
|
812
811
|
description: config.description,
|
|
@@ -817,7 +816,7 @@ var genBrainAtom = (input) => {
|
|
|
817
816
|
*/
|
|
818
817
|
ask: async (askInput, context) => {
|
|
819
818
|
const startTime = Date.now();
|
|
820
|
-
const systemPrompt = askInput.role.briefs ? await (0,
|
|
819
|
+
const systemPrompt = askInput.role.briefs ? await (0, import_brains.castBriefsToPrompt)({ briefs: askInput.role.briefs }) : void 0;
|
|
821
820
|
const openai = context?.openai ?? new import_openai.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
822
821
|
const jsonSchema = import_zod.z.toJSONSchema(askInput.schema.output);
|
|
823
822
|
const isObjectSchema = typeof jsonSchema === "object" && jsonSchema !== null && "type" in jsonSchema && jsonSchema.type === "object";
|
|
@@ -826,9 +825,17 @@ var genBrainAtom = (input) => {
|
|
|
826
825
|
---
|
|
827
826
|
|
|
828
827
|
${askInput.prompt}` : askInput.prompt;
|
|
828
|
+
const priorMessages = askInput.on?.episode?.exchanges.flatMap((exchange) => [
|
|
829
|
+
{ role: "user", content: exchange.input },
|
|
830
|
+
{ role: "assistant", content: exchange.output }
|
|
831
|
+
]) ?? [];
|
|
832
|
+
const inputMessages = [
|
|
833
|
+
...priorMessages,
|
|
834
|
+
{ role: "user", content: fullPrompt }
|
|
835
|
+
];
|
|
829
836
|
const response = await openai.responses.create({
|
|
830
837
|
model: config.model,
|
|
831
|
-
input:
|
|
838
|
+
input: inputMessages,
|
|
832
839
|
...isObjectSchema && {
|
|
833
840
|
text: {
|
|
834
841
|
format: {
|
|
@@ -863,11 +870,11 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
863
870
|
cache: { get: 0, set: 0 }
|
|
864
871
|
}
|
|
865
872
|
};
|
|
866
|
-
const { cash } = (0,
|
|
873
|
+
const { cash } = (0, import_brains.calcBrainOutputCost)({
|
|
867
874
|
for: { tokens: size.tokens },
|
|
868
875
|
with: { cost: { cash: config.spec.cost.cash } }
|
|
869
876
|
});
|
|
870
|
-
const metrics = new
|
|
877
|
+
const metrics = new import_brains.BrainOutputMetrics({
|
|
871
878
|
size,
|
|
872
879
|
cost: {
|
|
873
880
|
time: { milliseconds: elapsedMs },
|
|
@@ -875,7 +882,26 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
875
882
|
}
|
|
876
883
|
});
|
|
877
884
|
const output = isObjectSchema ? askInput.schema.output.parse(JSON.parse(content)) : askInput.schema.output.parse(content);
|
|
878
|
-
|
|
885
|
+
const continuables = await (0, import_brains.genBrainContinuables)({
|
|
886
|
+
for: { grain: "atom" },
|
|
887
|
+
on: {
|
|
888
|
+
episode: askInput.on?.episode ?? null,
|
|
889
|
+
series: null
|
|
890
|
+
},
|
|
891
|
+
with: {
|
|
892
|
+
exchange: {
|
|
893
|
+
input: askInput.prompt,
|
|
894
|
+
output: content,
|
|
895
|
+
exid: response.id
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
return new import_brains.BrainOutput({
|
|
900
|
+
output,
|
|
901
|
+
metrics,
|
|
902
|
+
episode: continuables.episode,
|
|
903
|
+
series: continuables.series
|
|
904
|
+
});
|
|
879
905
|
}
|
|
880
906
|
});
|
|
881
907
|
};
|
|
@@ -1235,8 +1261,8 @@ var Codex = class {
|
|
|
1235
1261
|
};
|
|
1236
1262
|
|
|
1237
1263
|
// src/domain.operations/repls/genBrainRepl.ts
|
|
1238
|
-
var
|
|
1239
|
-
var
|
|
1264
|
+
var import_helpful_errors = require("helpful-errors");
|
|
1265
|
+
var import_brains2 = require("rhachet/brains");
|
|
1240
1266
|
var import_wrapper_fns = require("wrapper-fns");
|
|
1241
1267
|
|
|
1242
1268
|
// src/infra/schema/asJsonSchema.ts
|
|
@@ -1267,9 +1293,17 @@ var composePromptWithSystem = (userPrompt, systemPrompt) => {
|
|
|
1267
1293
|
|
|
1268
1294
|
${userPrompt}`;
|
|
1269
1295
|
};
|
|
1296
|
+
var EXID_PREFIX = "openai/codex";
|
|
1297
|
+
var encodeExid = (threadId) => `${EXID_PREFIX}/${threadId}`;
|
|
1298
|
+
var decodeExid = (exid) => {
|
|
1299
|
+
if (!exid.startsWith(`${EXID_PREFIX}/`)) return { valid: false };
|
|
1300
|
+
const threadId = exid.slice(`${EXID_PREFIX}/`.length);
|
|
1301
|
+
if (!threadId) return { valid: false };
|
|
1302
|
+
return { valid: true, threadId };
|
|
1303
|
+
};
|
|
1270
1304
|
var invokeCodex = async (input) => {
|
|
1271
1305
|
const startTime = Date.now();
|
|
1272
|
-
const systemPrompt = input.role.briefs ? await (0,
|
|
1306
|
+
const systemPrompt = input.role.briefs ? await (0, import_brains2.castBriefsToPrompt)({ briefs: input.role.briefs }) : void 0;
|
|
1273
1307
|
const outputSchema = asJsonSchema({
|
|
1274
1308
|
schema: input.schema.output
|
|
1275
1309
|
});
|
|
@@ -1277,10 +1311,18 @@ var invokeCodex = async (input) => {
|
|
|
1277
1311
|
apiKey: process.env.OPENAI_API_KEY
|
|
1278
1312
|
});
|
|
1279
1313
|
const sandboxMode = input.mode === "ask" ? "read-only" : "workspace-write";
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1314
|
+
const threadOptions = { model: input.model, sandboxMode };
|
|
1315
|
+
const priorExid = input.on?.episode?.exid ?? null;
|
|
1316
|
+
const thread = (() => {
|
|
1317
|
+
if (!priorExid) return codex.startThread(threadOptions);
|
|
1318
|
+
const decoded = decodeExid(priorExid);
|
|
1319
|
+
if (!decoded.valid)
|
|
1320
|
+
throw new import_helpful_errors.BadRequestError(
|
|
1321
|
+
"episode continuation failed: exid is not from openai/codex. cross-supplier continuation is not supported.",
|
|
1322
|
+
{ priorExid }
|
|
1323
|
+
);
|
|
1324
|
+
return codex.resumeThread(decoded.threadId, threadOptions);
|
|
1325
|
+
})();
|
|
1284
1326
|
const fullPrompt = composePromptWithSystem(input.prompt, systemPrompt);
|
|
1285
1327
|
const response = await (0, import_wrapper_fns.withRetry)(
|
|
1286
1328
|
(0, import_wrapper_fns.withTimeout)(async () => thread.run(fullPrompt, { outputSchema }), {
|
|
@@ -1313,22 +1355,43 @@ var invokeCodex = async (input) => {
|
|
|
1313
1355
|
cache: { get: 0, set: 0 }
|
|
1314
1356
|
}
|
|
1315
1357
|
};
|
|
1316
|
-
const { cash } = (0,
|
|
1358
|
+
const { cash } = (0, import_brains2.calcBrainOutputCost)({
|
|
1317
1359
|
for: { tokens: size.tokens },
|
|
1318
1360
|
with: { cost: { cash: input.spec.cost.cash } }
|
|
1319
1361
|
});
|
|
1320
|
-
const metrics = new
|
|
1362
|
+
const metrics = new import_brains2.BrainOutputMetrics({
|
|
1321
1363
|
size,
|
|
1322
1364
|
cost: {
|
|
1323
1365
|
time: { milliseconds: elapsedMs },
|
|
1324
1366
|
cash
|
|
1325
1367
|
}
|
|
1326
1368
|
});
|
|
1327
|
-
|
|
1369
|
+
const threadExid = thread.id ? encodeExid(thread.id) : null;
|
|
1370
|
+
const continuables = await (0, import_brains2.genBrainContinuables)({
|
|
1371
|
+
for: { grain: "repl" },
|
|
1372
|
+
on: {
|
|
1373
|
+
episode: input.on?.episode ?? null,
|
|
1374
|
+
series: input.on?.series ?? null
|
|
1375
|
+
},
|
|
1376
|
+
with: {
|
|
1377
|
+
exchange: {
|
|
1378
|
+
input: input.prompt,
|
|
1379
|
+
output: content,
|
|
1380
|
+
exid: thread.id
|
|
1381
|
+
},
|
|
1382
|
+
episode: { exid: threadExid }
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
return new import_brains2.BrainOutput({
|
|
1386
|
+
output,
|
|
1387
|
+
metrics,
|
|
1388
|
+
episode: continuables.episode,
|
|
1389
|
+
series: continuables.series
|
|
1390
|
+
});
|
|
1328
1391
|
};
|
|
1329
1392
|
var genBrainRepl = (input) => {
|
|
1330
1393
|
const config = CONFIG_BY_REPL_SLUG[input.slug];
|
|
1331
|
-
return new
|
|
1394
|
+
return new import_brains2.BrainRepl({
|
|
1332
1395
|
repo: "openai",
|
|
1333
1396
|
slug: input.slug,
|
|
1334
1397
|
description: config.description,
|
|
@@ -1341,7 +1404,11 @@ var genBrainRepl = (input) => {
|
|
|
1341
1404
|
mode: "ask",
|
|
1342
1405
|
model: config.model,
|
|
1343
1406
|
spec: config.spec,
|
|
1344
|
-
|
|
1407
|
+
on: askInput.on,
|
|
1408
|
+
plugs: askInput.plugs,
|
|
1409
|
+
role: askInput.role,
|
|
1410
|
+
prompt: askInput.prompt,
|
|
1411
|
+
schema: askInput.schema
|
|
1345
1412
|
}),
|
|
1346
1413
|
/**
|
|
1347
1414
|
* .what = read+write actions (code changes, file edits)
|
|
@@ -1351,7 +1418,11 @@ var genBrainRepl = (input) => {
|
|
|
1351
1418
|
mode: "act",
|
|
1352
1419
|
model: config.model,
|
|
1353
1420
|
spec: config.spec,
|
|
1354
|
-
|
|
1421
|
+
on: actInput.on,
|
|
1422
|
+
plugs: actInput.plugs,
|
|
1423
|
+
role: actInput.role,
|
|
1424
|
+
prompt: actInput.prompt,
|
|
1425
|
+
schema: actInput.schema
|
|
1355
1426
|
})
|
|
1356
1427
|
});
|
|
1357
1428
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "rhachet-brains-openai",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "rhachet brain.atom and brain.repl adapter for openai",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.1",
|
|
6
6
|
"repository": "ehmpathy/rhachet-brains-openai",
|
|
7
7
|
"homepage": "https://github.com/ehmpathy/rhachet-brains-openai",
|
|
8
8
|
"keywords": [
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"preversion": "npm run prepush",
|
|
55
55
|
"postversion": "git push origin HEAD --tags --no-verify",
|
|
56
56
|
"prepare:husky": "husky install && chmod ug+x .husky/*",
|
|
57
|
-
"prepare:rhachet": "rhachet init --
|
|
57
|
+
"prepare:rhachet": "rhachet init --hooks --roles behaver mechanic reviewer",
|
|
58
58
|
"prepare": "if [ -e .git ] && [ -z $CI ]; then npm run prepare:husky && npm run prepare:rhachet; fi"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
@@ -90,10 +90,11 @@
|
|
|
90
90
|
"husky": "8.0.3",
|
|
91
91
|
"iso-time": "1.11.1",
|
|
92
92
|
"jest": "30.2.0",
|
|
93
|
-
"rhachet": "1.
|
|
94
|
-
"rhachet-
|
|
95
|
-
"rhachet-roles-
|
|
96
|
-
"rhachet-roles-
|
|
93
|
+
"rhachet": "1.29.5",
|
|
94
|
+
"rhachet-brains-anthropic": "0.3.2",
|
|
95
|
+
"rhachet-roles-bhrain": "0.7.5",
|
|
96
|
+
"rhachet-roles-bhuild": "0.7.0",
|
|
97
|
+
"rhachet-roles-ehmpathy": "1.18.0",
|
|
97
98
|
"test-fns": "1.10.0",
|
|
98
99
|
"tsc-alias": "1.8.10",
|
|
99
100
|
"tsx": "4.20.6",
|
package/readme.md
CHANGED
|
@@ -51,6 +51,60 @@ const { output: { proposal } } = await brainRepl.act({
|
|
|
51
51
|
});
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
## continuation support
|
|
55
|
+
|
|
56
|
+
both atoms and repls support multi-turn conversations via episode continuation.
|
|
57
|
+
|
|
58
|
+
### atoms
|
|
59
|
+
|
|
60
|
+
atoms support continuation via the openai responses api. pass the prior episode to continue the conversation:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// first call establishes context
|
|
64
|
+
const resultFirst = await brainAtom.ask({
|
|
65
|
+
role: {},
|
|
66
|
+
prompt: 'remember the secret word "PINEAPPLE42"',
|
|
67
|
+
schema: { output: z.object({ content: z.string() }) },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// second call continues with prior context
|
|
71
|
+
const resultSecond = await brainAtom.ask({
|
|
72
|
+
on: { episode: resultFirst.episode },
|
|
73
|
+
role: {},
|
|
74
|
+
prompt: 'what is the secret word I told you?',
|
|
75
|
+
schema: { output: z.object({ content: z.string() }) },
|
|
76
|
+
});
|
|
77
|
+
// resultSecond.output.content contains "PINEAPPLE42"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### repls
|
|
81
|
+
|
|
82
|
+
repls support continuation via the codex-sdk `resumeThread()` api. the episode exid contains the thread id prefixed with `openai/codex/` for cross-supplier validation:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// first call starts a new thread
|
|
86
|
+
const resultFirst = await brainRepl.ask({
|
|
87
|
+
role: {},
|
|
88
|
+
prompt: 'remember the secret word "MANGO99"',
|
|
89
|
+
schema: { output: z.object({ content: z.string() }) },
|
|
90
|
+
});
|
|
91
|
+
// resultFirst.episode.exid = "openai/codex/{threadId}"
|
|
92
|
+
|
|
93
|
+
// second call resumes the thread
|
|
94
|
+
const resultSecond = await brainRepl.ask({
|
|
95
|
+
on: { episode: resultFirst.episode },
|
|
96
|
+
role: {},
|
|
97
|
+
prompt: 'what is the secret word I told you?',
|
|
98
|
+
schema: { output: z.object({ content: z.string() }) },
|
|
99
|
+
});
|
|
100
|
+
// resultSecond.output.content contains "MANGO99"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### limitations
|
|
104
|
+
|
|
105
|
+
- **cross-supplier continuation is not supported**: episodes from other brain suppliers (e.g., anthropic) cannot be used to continue openai conversations. this throws a `BadRequestError`.
|
|
106
|
+
- **exid validation**: the episode exid must start with `openai/codex/` for repl continuation. this prevents accidental cross-supplier continuation attempts.
|
|
107
|
+
|
|
54
108
|
## available brains
|
|
55
109
|
|
|
56
110
|
### atoms (via genBrainAtom)
|