rhachet-brains-openai 0.2.0 → 0.3.0
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/domain.operations/atoms/genBrainAtom.d.ts +1 -1
- package/dist/domain.operations/repls/genBrainRepl.d.ts +1 -1
- package/dist/index.js +215 -49
- package/dist/infra/cast/castFromOpenaiFunctionCall.d.ts +9 -0
- package/dist/infra/cast/castIntoOpenaiFunctionCallOutput.d.ts +12 -0
- package/dist/infra/cast/castIntoOpenaiFunctionTool.d.ts +14 -0
- package/package.json +7 -6
- package/readme.md +54 -0
package/dist/index.js
CHANGED
|
@@ -39,9 +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");
|
|
44
|
-
var import_zod = require("zod");
|
|
42
|
+
var import_brains = require("rhachet/brains");
|
|
45
43
|
|
|
46
44
|
// src/domain.objects/BrainAtom.config.ts
|
|
47
45
|
var import_iso_price = require("iso-price");
|
|
@@ -803,33 +801,109 @@ var CONFIG_BY_ATOM_SLUG = {
|
|
|
803
801
|
}
|
|
804
802
|
};
|
|
805
803
|
|
|
804
|
+
// src/infra/cast/castFromOpenaiFunctionCall.ts
|
|
805
|
+
var castFromOpenaiFunctionCall = (input) => ({
|
|
806
|
+
exid: input.item.call_id,
|
|
807
|
+
slug: input.item.name,
|
|
808
|
+
input: JSON.parse(input.item.arguments)
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// src/infra/cast/castIntoOpenaiFunctionCallOutput.ts
|
|
812
|
+
var genFunctionCallOutputId = () => `fc_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
813
|
+
var castIntoOpenaiFunctionCallOutput = (input) => ({
|
|
814
|
+
type: "function_call_output",
|
|
815
|
+
id: genFunctionCallOutputId(),
|
|
816
|
+
call_id: input.execution.exid,
|
|
817
|
+
output: JSON.stringify(input.execution.output)
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// src/infra/schema/asJsonSchema.ts
|
|
821
|
+
var import_zod = require("zod");
|
|
822
|
+
var asJsonSchema = (input) => {
|
|
823
|
+
return import_zod.z.toJSONSchema(input.schema, { target: "openAi" });
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
// src/infra/cast/castIntoOpenaiFunctionTool.ts
|
|
827
|
+
var asOpenaiFunctionName = (slug) => slug.replace(/\./g, "_");
|
|
828
|
+
var castIntoOpenaiFunctionTool = (input) => ({
|
|
829
|
+
type: "function",
|
|
830
|
+
name: asOpenaiFunctionName(input.definition.slug),
|
|
831
|
+
description: input.definition.description,
|
|
832
|
+
parameters: asJsonSchema({ schema: input.definition.schema.input }),
|
|
833
|
+
strict: true
|
|
834
|
+
});
|
|
835
|
+
|
|
806
836
|
// src/domain.operations/atoms/genBrainAtom.ts
|
|
837
|
+
var reconstructAssistantItems = (exchangeOutput) => {
|
|
838
|
+
try {
|
|
839
|
+
const parsed = JSON.parse(exchangeOutput);
|
|
840
|
+
if (Array.isArray(parsed)) {
|
|
841
|
+
const hasResponseItems = parsed.some(
|
|
842
|
+
(item) => item?.type === "function_call" || item?.type === "message" || item?.type === "reasoning"
|
|
843
|
+
);
|
|
844
|
+
if (hasResponseItems) {
|
|
845
|
+
return parsed;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
850
|
+
}
|
|
851
|
+
return [{ role: "assistant", content: exchangeOutput }];
|
|
852
|
+
};
|
|
807
853
|
var genBrainAtom = (input) => {
|
|
808
854
|
const config = CONFIG_BY_ATOM_SLUG[input.slug];
|
|
809
|
-
return new
|
|
855
|
+
return new import_brains.BrainAtom({
|
|
810
856
|
repo: "openai",
|
|
811
857
|
slug: input.slug,
|
|
812
858
|
description: config.description,
|
|
813
859
|
spec: config.spec,
|
|
814
860
|
/**
|
|
815
|
-
* .what = stateless inference
|
|
816
|
-
* .why = provides direct model access for reason tasks
|
|
861
|
+
* .what = stateless inference with optional tool use
|
|
862
|
+
* .why = provides direct model access for reason tasks, supports tool invocations
|
|
817
863
|
*/
|
|
818
864
|
ask: async (askInput, context) => {
|
|
819
865
|
const startTime = Date.now();
|
|
820
|
-
const systemPrompt = askInput.role.briefs ? await (0,
|
|
866
|
+
const systemPrompt = askInput.role.briefs ? await (0, import_brains.castBriefsToPrompt)({ briefs: askInput.role.briefs }) : void 0;
|
|
821
867
|
const openai = context?.openai ?? new import_openai.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
822
|
-
const jsonSchema =
|
|
868
|
+
const jsonSchema = asJsonSchema({ schema: askInput.schema.output });
|
|
823
869
|
const isObjectSchema = typeof jsonSchema === "object" && jsonSchema !== null && "type" in jsonSchema && jsonSchema.type === "object";
|
|
824
|
-
const
|
|
870
|
+
const isToolExecutionArray = Array.isArray(askInput.prompt);
|
|
871
|
+
const pluggedTools = askInput.plugs?.tools ?? [];
|
|
872
|
+
const hasTools = pluggedTools.length > 0;
|
|
873
|
+
const fullPrompt = isToolExecutionArray ? null : systemPrompt ? `${systemPrompt}
|
|
825
874
|
|
|
826
875
|
---
|
|
827
876
|
|
|
828
877
|
${askInput.prompt}` : askInput.prompt;
|
|
878
|
+
const openaiFunctionTools = hasTools ? pluggedTools.map(
|
|
879
|
+
(definition) => castIntoOpenaiFunctionTool({ definition })
|
|
880
|
+
) : [];
|
|
881
|
+
const priorMessages = askInput.on?.episode?.exchanges.flatMap((exchange) => [
|
|
882
|
+
{ role: "user", content: exchange.input },
|
|
883
|
+
...reconstructAssistantItems(exchange.output)
|
|
884
|
+
]) ?? [];
|
|
885
|
+
const currentInput = isToolExecutionArray ? askInput.prompt.map(
|
|
886
|
+
(execution) => castIntoOpenaiFunctionCallOutput({ execution })
|
|
887
|
+
) : [{ role: "user", content: fullPrompt }];
|
|
888
|
+
const systemInstruction = hasTools && isObjectSchema ? [
|
|
889
|
+
{
|
|
890
|
+
role: "system",
|
|
891
|
+
content: `When you have the final answer, respond with valid JSON matching this schema:
|
|
892
|
+
${JSON.stringify(jsonSchema, null, 2)}`
|
|
893
|
+
}
|
|
894
|
+
] : [];
|
|
895
|
+
const inputMessages = [
|
|
896
|
+
...systemInstruction,
|
|
897
|
+
...priorMessages,
|
|
898
|
+
...currentInput
|
|
899
|
+
];
|
|
829
900
|
const response = await openai.responses.create({
|
|
830
901
|
model: config.model,
|
|
831
|
-
input:
|
|
832
|
-
...
|
|
902
|
+
input: inputMessages,
|
|
903
|
+
...hasTools && {
|
|
904
|
+
tools: openaiFunctionTools
|
|
905
|
+
},
|
|
906
|
+
...!hasTools && isObjectSchema && {
|
|
833
907
|
text: {
|
|
834
908
|
format: {
|
|
835
909
|
type: "json_schema",
|
|
@@ -840,6 +914,14 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
840
914
|
}
|
|
841
915
|
}
|
|
842
916
|
});
|
|
917
|
+
const functionCallItems = response.output.filter(
|
|
918
|
+
(item) => item.type === "function_call"
|
|
919
|
+
);
|
|
920
|
+
const calls = functionCallItems.length > 0 ? {
|
|
921
|
+
tools: functionCallItems.map(
|
|
922
|
+
(item) => castFromOpenaiFunctionCall({ item })
|
|
923
|
+
)
|
|
924
|
+
} : null;
|
|
843
925
|
const outputItem = response.output.find(
|
|
844
926
|
(item) => item.type === "message"
|
|
845
927
|
);
|
|
@@ -849,7 +931,7 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
849
931
|
const tokensOutput = response.usage?.output_tokens ?? 0;
|
|
850
932
|
const tokensCacheGet = response.usage?.input_tokens_details?.cached_tokens ?? 0;
|
|
851
933
|
const elapsedMs = Date.now() - startTime;
|
|
852
|
-
const charsInput = fullPrompt.length;
|
|
934
|
+
const charsInput = isToolExecutionArray ? JSON.stringify(askInput.prompt).length : fullPrompt.length;
|
|
853
935
|
const charsOutput = content.length;
|
|
854
936
|
const size = {
|
|
855
937
|
tokens: {
|
|
@@ -863,19 +945,70 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
863
945
|
cache: { get: 0, set: 0 }
|
|
864
946
|
}
|
|
865
947
|
};
|
|
866
|
-
const { cash } = (0,
|
|
948
|
+
const { cash } = (0, import_brains.calcBrainOutputCost)({
|
|
867
949
|
for: { tokens: size.tokens },
|
|
868
950
|
with: { cost: { cash: config.spec.cost.cash } }
|
|
869
951
|
});
|
|
870
|
-
const metrics = new
|
|
952
|
+
const metrics = new import_brains.BrainOutputMetrics({
|
|
871
953
|
size,
|
|
872
954
|
cost: {
|
|
873
955
|
time: { milliseconds: elapsedMs },
|
|
874
956
|
cash
|
|
875
957
|
}
|
|
876
958
|
});
|
|
877
|
-
const output =
|
|
878
|
-
|
|
959
|
+
const output = (() => {
|
|
960
|
+
if (!content) return null;
|
|
961
|
+
if (hasTools && isObjectSchema) {
|
|
962
|
+
try {
|
|
963
|
+
return askInput.schema.output.parse(JSON.parse(content));
|
|
964
|
+
} catch (error) {
|
|
965
|
+
const isParseError = error instanceof SyntaxError || error?.constructor?.name === "ZodError";
|
|
966
|
+
if (!isParseError) throw error;
|
|
967
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
968
|
+
if (jsonMatch) {
|
|
969
|
+
try {
|
|
970
|
+
return askInput.schema.output.parse(JSON.parse(jsonMatch[0]));
|
|
971
|
+
} catch (extractError) {
|
|
972
|
+
const isExtractParseError = extractError instanceof SyntaxError || extractError?.constructor?.name === "ZodError";
|
|
973
|
+
if (!isExtractParseError) throw extractError;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (calls) return null;
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
return isObjectSchema ? askInput.schema.output.parse(JSON.parse(content)) : askInput.schema.output.parse(content);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
const isParseError = error instanceof SyntaxError || error?.constructor?.name === "ZodError";
|
|
984
|
+
if (!isParseError) throw error;
|
|
985
|
+
if (calls) return null;
|
|
986
|
+
throw new Error("structured output parse failed with no tool calls");
|
|
987
|
+
}
|
|
988
|
+
})();
|
|
989
|
+
const exchangeInput = isToolExecutionArray ? JSON.stringify(askInput.prompt) : askInput.prompt;
|
|
990
|
+
const exchangeOutput = functionCallItems.length > 0 ? JSON.stringify(response.output) : content;
|
|
991
|
+
const continuables = await (0, import_brains.genBrainContinuables)({
|
|
992
|
+
for: { grain: "atom" },
|
|
993
|
+
on: {
|
|
994
|
+
episode: askInput.on?.episode ?? null,
|
|
995
|
+
series: null
|
|
996
|
+
},
|
|
997
|
+
with: {
|
|
998
|
+
exchange: {
|
|
999
|
+
input: exchangeInput,
|
|
1000
|
+
output: exchangeOutput,
|
|
1001
|
+
exid: response.id
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
return new import_brains.BrainOutput({
|
|
1006
|
+
output,
|
|
1007
|
+
calls,
|
|
1008
|
+
metrics,
|
|
1009
|
+
episode: continuables.episode,
|
|
1010
|
+
series: continuables.series
|
|
1011
|
+
});
|
|
879
1012
|
}
|
|
880
1013
|
});
|
|
881
1014
|
};
|
|
@@ -1235,17 +1368,9 @@ var Codex = class {
|
|
|
1235
1368
|
};
|
|
1236
1369
|
|
|
1237
1370
|
// src/domain.operations/repls/genBrainRepl.ts
|
|
1238
|
-
var
|
|
1239
|
-
var
|
|
1371
|
+
var import_helpful_errors = require("helpful-errors");
|
|
1372
|
+
var import_brains2 = require("rhachet/brains");
|
|
1240
1373
|
var import_wrapper_fns = require("wrapper-fns");
|
|
1241
|
-
|
|
1242
|
-
// src/infra/schema/asJsonSchema.ts
|
|
1243
|
-
var import_zod2 = require("zod");
|
|
1244
|
-
var asJsonSchema = (input) => {
|
|
1245
|
-
return import_zod2.z.toJSONSchema(input.schema, { target: "openAi" });
|
|
1246
|
-
};
|
|
1247
|
-
|
|
1248
|
-
// src/domain.operations/repls/genBrainRepl.ts
|
|
1249
1374
|
var CONFIG_BY_REPL_SLUG = {
|
|
1250
1375
|
// default
|
|
1251
1376
|
"openai/codex": CONFIG_BY_ATOM_SLUG["openai/gpt/codex/5.1-max"],
|
|
@@ -1267,9 +1392,17 @@ var composePromptWithSystem = (userPrompt, systemPrompt) => {
|
|
|
1267
1392
|
|
|
1268
1393
|
${userPrompt}`;
|
|
1269
1394
|
};
|
|
1395
|
+
var EXID_PREFIX = "openai/codex";
|
|
1396
|
+
var encodeExid = (threadId) => `${EXID_PREFIX}/${threadId}`;
|
|
1397
|
+
var decodeExid = (exid) => {
|
|
1398
|
+
if (!exid.startsWith(`${EXID_PREFIX}/`)) return { valid: false };
|
|
1399
|
+
const threadId = exid.slice(`${EXID_PREFIX}/`.length);
|
|
1400
|
+
if (!threadId) return { valid: false };
|
|
1401
|
+
return { valid: true, threadId };
|
|
1402
|
+
};
|
|
1270
1403
|
var invokeCodex = async (input) => {
|
|
1271
1404
|
const startTime = Date.now();
|
|
1272
|
-
const systemPrompt = input.role.briefs ? await (0,
|
|
1405
|
+
const systemPrompt = input.role.briefs ? await (0, import_brains2.castBriefsToPrompt)({ briefs: input.role.briefs }) : void 0;
|
|
1273
1406
|
const outputSchema = asJsonSchema({
|
|
1274
1407
|
schema: input.schema.output
|
|
1275
1408
|
});
|
|
@@ -1277,10 +1410,18 @@ var invokeCodex = async (input) => {
|
|
|
1277
1410
|
apiKey: process.env.OPENAI_API_KEY
|
|
1278
1411
|
});
|
|
1279
1412
|
const sandboxMode = input.mode === "ask" ? "read-only" : "workspace-write";
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1413
|
+
const threadOptions = { model: input.model, sandboxMode };
|
|
1414
|
+
const priorExid = input.on?.episode?.exid ?? null;
|
|
1415
|
+
const thread = (() => {
|
|
1416
|
+
if (!priorExid) return codex.startThread(threadOptions);
|
|
1417
|
+
const decoded = decodeExid(priorExid);
|
|
1418
|
+
if (!decoded.valid)
|
|
1419
|
+
throw new import_helpful_errors.BadRequestError(
|
|
1420
|
+
"episode continuation failed: exid is not from openai/codex. cross-supplier continuation is not supported.",
|
|
1421
|
+
{ priorExid }
|
|
1422
|
+
);
|
|
1423
|
+
return codex.resumeThread(decoded.threadId, threadOptions);
|
|
1424
|
+
})();
|
|
1284
1425
|
const fullPrompt = composePromptWithSystem(input.prompt, systemPrompt);
|
|
1285
1426
|
const response = await (0, import_wrapper_fns.withRetry)(
|
|
1286
1427
|
(0, import_wrapper_fns.withTimeout)(async () => thread.run(fullPrompt, { outputSchema }), {
|
|
@@ -1313,46 +1454,71 @@ var invokeCodex = async (input) => {
|
|
|
1313
1454
|
cache: { get: 0, set: 0 }
|
|
1314
1455
|
}
|
|
1315
1456
|
};
|
|
1316
|
-
const { cash } = (0,
|
|
1457
|
+
const { cash } = (0, import_brains2.calcBrainOutputCost)({
|
|
1317
1458
|
for: { tokens: size.tokens },
|
|
1318
1459
|
with: { cost: { cash: input.spec.cost.cash } }
|
|
1319
1460
|
});
|
|
1320
|
-
const metrics = new
|
|
1461
|
+
const metrics = new import_brains2.BrainOutputMetrics({
|
|
1321
1462
|
size,
|
|
1322
1463
|
cost: {
|
|
1323
1464
|
time: { milliseconds: elapsedMs },
|
|
1324
1465
|
cash
|
|
1325
1466
|
}
|
|
1326
1467
|
});
|
|
1327
|
-
|
|
1468
|
+
const threadExid = thread.id ? encodeExid(thread.id) : null;
|
|
1469
|
+
const continuables = await (0, import_brains2.genBrainContinuables)({
|
|
1470
|
+
for: { grain: "repl" },
|
|
1471
|
+
on: {
|
|
1472
|
+
episode: input.on?.episode ?? null,
|
|
1473
|
+
series: input.on?.series ?? null
|
|
1474
|
+
},
|
|
1475
|
+
with: {
|
|
1476
|
+
exchange: {
|
|
1477
|
+
input: input.prompt,
|
|
1478
|
+
output: content,
|
|
1479
|
+
exid: thread.id
|
|
1480
|
+
},
|
|
1481
|
+
episode: { exid: threadExid }
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
return new import_brains2.BrainOutput({
|
|
1485
|
+
output,
|
|
1486
|
+
calls: null,
|
|
1487
|
+
metrics,
|
|
1488
|
+
episode: continuables.episode,
|
|
1489
|
+
series: continuables.series
|
|
1490
|
+
});
|
|
1328
1491
|
};
|
|
1329
1492
|
var genBrainRepl = (input) => {
|
|
1330
1493
|
const config = CONFIG_BY_REPL_SLUG[input.slug];
|
|
1331
|
-
return new
|
|
1494
|
+
return new import_brains2.BrainRepl({
|
|
1332
1495
|
repo: "openai",
|
|
1333
1496
|
slug: input.slug,
|
|
1334
1497
|
description: config.description,
|
|
1335
1498
|
spec: config.spec,
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
ask: async (askInput, _context) => invokeCodex({
|
|
1499
|
+
// note: repls only accept string prompts (tool execution handled internally by codex SDK)
|
|
1500
|
+
// type assertions needed because BrainRepl contract now supports AsBrainPromptFor<TPlugs>
|
|
1501
|
+
// but codex SDK's thread.run() only accepts string prompts
|
|
1502
|
+
ask: (async (askInput, _context) => invokeCodex({
|
|
1341
1503
|
mode: "ask",
|
|
1342
1504
|
model: config.model,
|
|
1343
1505
|
spec: config.spec,
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
act: async (actInput, _context) => invokeCodex({
|
|
1506
|
+
on: askInput.on,
|
|
1507
|
+
plugs: askInput.plugs,
|
|
1508
|
+
role: askInput.role,
|
|
1509
|
+
prompt: askInput.prompt,
|
|
1510
|
+
schema: askInput.schema
|
|
1511
|
+
})),
|
|
1512
|
+
act: (async (actInput, _context) => invokeCodex({
|
|
1351
1513
|
mode: "act",
|
|
1352
1514
|
model: config.model,
|
|
1353
1515
|
spec: config.spec,
|
|
1354
|
-
|
|
1355
|
-
|
|
1516
|
+
on: actInput.on,
|
|
1517
|
+
plugs: actInput.plugs,
|
|
1518
|
+
role: actInput.role,
|
|
1519
|
+
prompt: actInput.prompt,
|
|
1520
|
+
schema: actInput.schema
|
|
1521
|
+
}))
|
|
1356
1522
|
});
|
|
1357
1523
|
};
|
|
1358
1524
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
import type { BrainPlugToolInvocation } from 'rhachet/brains';
|
|
3
|
+
/**
|
|
4
|
+
* .what = cast openai function_call to rhachet tool invocation
|
|
5
|
+
* .why = explicit boundary between openai sdk and rhachet domain
|
|
6
|
+
*/
|
|
7
|
+
export declare const castFromOpenaiFunctionCall: (input: {
|
|
8
|
+
item: OpenAI.Responses.ResponseFunctionToolCall;
|
|
9
|
+
}) => BrainPlugToolInvocation<unknown>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
import type { BrainPlugToolExecution } from 'rhachet/brains';
|
|
3
|
+
/**
|
|
4
|
+
* .what = cast rhachet tool execution to openai function_call_output format
|
|
5
|
+
* .why = explicit boundary between rhachet domain and openai sdk
|
|
6
|
+
*
|
|
7
|
+
* .note = id is a new generated id for this output item (must start with fc_)
|
|
8
|
+
* .note = call_id references the original function_call this responds to
|
|
9
|
+
*/
|
|
10
|
+
export declare const castIntoOpenaiFunctionCallOutput: (input: {
|
|
11
|
+
execution: BrainPlugToolExecution<unknown, unknown>;
|
|
12
|
+
}) => OpenAI.Responses.ResponseFunctionToolCallOutputItem;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
import type { BrainPlugToolDefinition } from 'rhachet/brains';
|
|
3
|
+
/**
|
|
4
|
+
* .what = transforms slug to valid openai function name
|
|
5
|
+
* .why = openai requires names match pattern ^[a-zA-Z0-9_-]+$
|
|
6
|
+
*/
|
|
7
|
+
export declare const asOpenaiFunctionName: (slug: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* .what = cast rhachet tool definition to openai function tool format
|
|
10
|
+
* .why = explicit boundary between rhachet domain and openai sdk
|
|
11
|
+
*/
|
|
12
|
+
export declare const castIntoOpenaiFunctionTool: (input: {
|
|
13
|
+
definition: BrainPlugToolDefinition<unknown, unknown, 'atom', string>;
|
|
14
|
+
}) => OpenAI.Responses.FunctionTool;
|
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.
|
|
5
|
+
"version": "0.3.0",
|
|
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": "
|
|
57
|
+
"prepare:rhachet": "npm run build && rhachet init --hooks --roles behaver driver mechanic reviewer architect ergonomist librarian",
|
|
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.37.17",
|
|
94
|
+
"rhachet-brains-anthropic": "0.3.3",
|
|
95
|
+
"rhachet-roles-bhrain": "0.20.0",
|
|
96
|
+
"rhachet-roles-bhuild": "0.14.2",
|
|
97
|
+
"rhachet-roles-ehmpathy": "1.29.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)
|