rhachet-brains-openai 0.2.1 → 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/index.js
CHANGED
|
@@ -40,7 +40,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
// src/domain.operations/atoms/genBrainAtom.ts
|
|
41
41
|
var import_openai = __toESM(require("openai"));
|
|
42
42
|
var import_brains = require("rhachet/brains");
|
|
43
|
-
var import_zod = require("zod");
|
|
44
43
|
|
|
45
44
|
// src/domain.objects/BrainAtom.config.ts
|
|
46
45
|
var import_iso_price = require("iso-price");
|
|
@@ -802,7 +801,55 @@ var CONFIG_BY_ATOM_SLUG = {
|
|
|
802
801
|
}
|
|
803
802
|
};
|
|
804
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
|
+
|
|
805
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
|
+
};
|
|
806
853
|
var genBrainAtom = (input) => {
|
|
807
854
|
const config = CONFIG_BY_ATOM_SLUG[input.slug];
|
|
808
855
|
return new import_brains.BrainAtom({
|
|
@@ -811,32 +858,52 @@ var genBrainAtom = (input) => {
|
|
|
811
858
|
description: config.description,
|
|
812
859
|
spec: config.spec,
|
|
813
860
|
/**
|
|
814
|
-
* .what = stateless inference
|
|
815
|
-
* .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
|
|
816
863
|
*/
|
|
817
864
|
ask: async (askInput, context) => {
|
|
818
865
|
const startTime = Date.now();
|
|
819
866
|
const systemPrompt = askInput.role.briefs ? await (0, import_brains.castBriefsToPrompt)({ briefs: askInput.role.briefs }) : void 0;
|
|
820
867
|
const openai = context?.openai ?? new import_openai.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
821
|
-
const jsonSchema =
|
|
868
|
+
const jsonSchema = asJsonSchema({ schema: askInput.schema.output });
|
|
822
869
|
const isObjectSchema = typeof jsonSchema === "object" && jsonSchema !== null && "type" in jsonSchema && jsonSchema.type === "object";
|
|
823
|
-
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}
|
|
824
874
|
|
|
825
875
|
---
|
|
826
876
|
|
|
827
877
|
${askInput.prompt}` : askInput.prompt;
|
|
878
|
+
const openaiFunctionTools = hasTools ? pluggedTools.map(
|
|
879
|
+
(definition) => castIntoOpenaiFunctionTool({ definition })
|
|
880
|
+
) : [];
|
|
828
881
|
const priorMessages = askInput.on?.episode?.exchanges.flatMap((exchange) => [
|
|
829
882
|
{ role: "user", content: exchange.input },
|
|
830
|
-
|
|
883
|
+
...reconstructAssistantItems(exchange.output)
|
|
831
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
|
+
] : [];
|
|
832
895
|
const inputMessages = [
|
|
896
|
+
...systemInstruction,
|
|
833
897
|
...priorMessages,
|
|
834
|
-
|
|
898
|
+
...currentInput
|
|
835
899
|
];
|
|
836
900
|
const response = await openai.responses.create({
|
|
837
901
|
model: config.model,
|
|
838
902
|
input: inputMessages,
|
|
839
|
-
...
|
|
903
|
+
...hasTools && {
|
|
904
|
+
tools: openaiFunctionTools
|
|
905
|
+
},
|
|
906
|
+
...!hasTools && isObjectSchema && {
|
|
840
907
|
text: {
|
|
841
908
|
format: {
|
|
842
909
|
type: "json_schema",
|
|
@@ -847,6 +914,14 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
847
914
|
}
|
|
848
915
|
}
|
|
849
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;
|
|
850
925
|
const outputItem = response.output.find(
|
|
851
926
|
(item) => item.type === "message"
|
|
852
927
|
);
|
|
@@ -856,7 +931,7 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
856
931
|
const tokensOutput = response.usage?.output_tokens ?? 0;
|
|
857
932
|
const tokensCacheGet = response.usage?.input_tokens_details?.cached_tokens ?? 0;
|
|
858
933
|
const elapsedMs = Date.now() - startTime;
|
|
859
|
-
const charsInput = fullPrompt.length;
|
|
934
|
+
const charsInput = isToolExecutionArray ? JSON.stringify(askInput.prompt).length : fullPrompt.length;
|
|
860
935
|
const charsOutput = content.length;
|
|
861
936
|
const size = {
|
|
862
937
|
tokens: {
|
|
@@ -881,7 +956,38 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
881
956
|
cash
|
|
882
957
|
}
|
|
883
958
|
});
|
|
884
|
-
const output =
|
|
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;
|
|
885
991
|
const continuables = await (0, import_brains.genBrainContinuables)({
|
|
886
992
|
for: { grain: "atom" },
|
|
887
993
|
on: {
|
|
@@ -890,14 +996,15 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
890
996
|
},
|
|
891
997
|
with: {
|
|
892
998
|
exchange: {
|
|
893
|
-
input:
|
|
894
|
-
output:
|
|
999
|
+
input: exchangeInput,
|
|
1000
|
+
output: exchangeOutput,
|
|
895
1001
|
exid: response.id
|
|
896
1002
|
}
|
|
897
1003
|
}
|
|
898
1004
|
});
|
|
899
1005
|
return new import_brains.BrainOutput({
|
|
900
1006
|
output,
|
|
1007
|
+
calls,
|
|
901
1008
|
metrics,
|
|
902
1009
|
episode: continuables.episode,
|
|
903
1010
|
series: continuables.series
|
|
@@ -1264,14 +1371,6 @@ var Codex = class {
|
|
|
1264
1371
|
var import_helpful_errors = require("helpful-errors");
|
|
1265
1372
|
var import_brains2 = require("rhachet/brains");
|
|
1266
1373
|
var import_wrapper_fns = require("wrapper-fns");
|
|
1267
|
-
|
|
1268
|
-
// src/infra/schema/asJsonSchema.ts
|
|
1269
|
-
var import_zod2 = require("zod");
|
|
1270
|
-
var asJsonSchema = (input) => {
|
|
1271
|
-
return import_zod2.z.toJSONSchema(input.schema, { target: "openAi" });
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
// src/domain.operations/repls/genBrainRepl.ts
|
|
1275
1374
|
var CONFIG_BY_REPL_SLUG = {
|
|
1276
1375
|
// default
|
|
1277
1376
|
"openai/codex": CONFIG_BY_ATOM_SLUG["openai/gpt/codex/5.1-max"],
|
|
@@ -1384,6 +1483,7 @@ var invokeCodex = async (input) => {
|
|
|
1384
1483
|
});
|
|
1385
1484
|
return new import_brains2.BrainOutput({
|
|
1386
1485
|
output,
|
|
1486
|
+
calls: null,
|
|
1387
1487
|
metrics,
|
|
1388
1488
|
episode: continuables.episode,
|
|
1389
1489
|
series: continuables.series
|
|
@@ -1396,11 +1496,10 @@ var genBrainRepl = (input) => {
|
|
|
1396
1496
|
slug: input.slug,
|
|
1397
1497
|
description: config.description,
|
|
1398
1498
|
spec: config.spec,
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
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({
|
|
1404
1503
|
mode: "ask",
|
|
1405
1504
|
model: config.model,
|
|
1406
1505
|
spec: config.spec,
|
|
@@ -1409,12 +1508,8 @@ var genBrainRepl = (input) => {
|
|
|
1409
1508
|
role: askInput.role,
|
|
1410
1509
|
prompt: askInput.prompt,
|
|
1411
1510
|
schema: askInput.schema
|
|
1412
|
-
}),
|
|
1413
|
-
|
|
1414
|
-
* .what = read+write actions (code changes, file edits)
|
|
1415
|
-
* .why = provides full agentic capabilities via workspace-write sandbox
|
|
1416
|
-
*/
|
|
1417
|
-
act: async (actInput, _context) => invokeCodex({
|
|
1511
|
+
})),
|
|
1512
|
+
act: (async (actInput, _context) => invokeCodex({
|
|
1418
1513
|
mode: "act",
|
|
1419
1514
|
model: config.model,
|
|
1420
1515
|
spec: config.spec,
|
|
@@ -1423,7 +1518,7 @@ var genBrainRepl = (input) => {
|
|
|
1423
1518
|
role: actInput.role,
|
|
1424
1519
|
prompt: actInput.prompt,
|
|
1425
1520
|
schema: actInput.schema
|
|
1426
|
-
})
|
|
1521
|
+
}))
|
|
1427
1522
|
});
|
|
1428
1523
|
};
|
|
1429
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": "rhachet init --hooks --roles behaver mechanic reviewer",
|
|
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,11 +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-brains-anthropic": "0.3.
|
|
95
|
-
"rhachet-roles-bhrain": "0.
|
|
96
|
-
"rhachet-roles-bhuild": "0.
|
|
97
|
-
"rhachet-roles-ehmpathy": "1.
|
|
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",
|
|
98
98
|
"test-fns": "1.10.0",
|
|
99
99
|
"tsc-alias": "1.8.10",
|
|
100
100
|
"tsx": "4.20.6",
|