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.
@@ -1,4 +1,4 @@
1
- import { BrainAtom } from 'rhachet';
1
+ import { BrainAtom } from 'rhachet/brains';
2
2
  import { type OpenaiBrainAtomSlug } from '../../domain.objects/BrainAtom.config';
3
3
  /**
4
4
  * .what = factory to generate openai brain atom instances
@@ -1,4 +1,4 @@
1
- import { BrainRepl } from 'rhachet';
1
+ import { BrainRepl } from 'rhachet/brains';
2
2
  /**
3
3
  * .what = supported openai brain repl slugs
4
4
  * .why = enables type-safe slug specification with model variants
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 import_rhachet = require("rhachet");
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 import_rhachet.BrainAtom({
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, import_rhachet.castBriefsToPrompt)({ briefs: askInput.role.briefs }) : void 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: fullPrompt,
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, import_calcBrainOutputCost.calcBrainOutputCost)({
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 import_rhachet.BrainOutputMetrics({
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
- return new import_rhachet.BrainOutput({ output, metrics });
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 import_rhachet2 = require("rhachet");
1239
- var import_calcBrainOutputCost2 = require("rhachet/dist/domain.operations/brainCost/calcBrainOutputCost");
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, import_rhachet2.castBriefsToPrompt)({ briefs: input.role.briefs }) : void 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 thread = codex.startThread({
1281
- model: input.model,
1282
- sandboxMode
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, import_calcBrainOutputCost2.calcBrainOutputCost)({
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 import_rhachet2.BrainOutputMetrics({
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
- return new import_rhachet2.BrainOutput({ output, metrics });
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 import_rhachet2.BrainRepl({
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
- ...askInput
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
- ...actInput
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.0",
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 --mode upsert && rhachet roles link --repo ehmpathy --role mechanic && rhachet roles link --repo bhuild --role behaver && rhachet roles link --repo bhrain --role reviewer && rhachet roles init --role mechanic && rhachet roles init --role behaver",
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.26.0",
94
- "rhachet-roles-bhrain": "0.5.9",
95
- "rhachet-roles-bhuild": "0.6.3",
96
- "rhachet-roles-ehmpathy": "1.17.15",
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)