rhachet-brains-openai 0.2.1 → 0.3.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 +160 -32
- 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/dist/infra/schema/asJsonSchema.d.ts +4 -1
- package/package.json +7 -7
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,88 @@ 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 asOpenaiStrictSchema = (schema) => {
|
|
823
|
+
if (!schema.properties) return schema;
|
|
824
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
825
|
+
const allPropertyNames = Object.keys(schema.properties);
|
|
826
|
+
const propertiesTransformed = {};
|
|
827
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
828
|
+
const isOptional = !requiredSet.has(name);
|
|
829
|
+
let propTransformed = { ...prop };
|
|
830
|
+
if (prop.type === "object" && prop.properties) {
|
|
831
|
+
propTransformed = asOpenaiStrictSchema(prop);
|
|
832
|
+
}
|
|
833
|
+
if (isOptional && propTransformed.type) {
|
|
834
|
+
const currentType = propTransformed.type;
|
|
835
|
+
if (Array.isArray(currentType)) {
|
|
836
|
+
if (!currentType.includes("null")) {
|
|
837
|
+
propTransformed = {
|
|
838
|
+
...propTransformed,
|
|
839
|
+
type: [...currentType, "null"]
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
propTransformed = { ...propTransformed, type: [currentType, "null"] };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
propertiesTransformed[name] = propTransformed;
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
...schema,
|
|
850
|
+
properties: propertiesTransformed,
|
|
851
|
+
required: allPropertyNames
|
|
852
|
+
};
|
|
853
|
+
};
|
|
854
|
+
var asJsonSchema = (input) => {
|
|
855
|
+
const schema = import_zod.z.toJSONSchema(input.schema, { target: "openAi" });
|
|
856
|
+
return asOpenaiStrictSchema(schema);
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// src/infra/cast/castIntoOpenaiFunctionTool.ts
|
|
860
|
+
var asOpenaiFunctionName = (slug) => slug.replace(/\./g, "_");
|
|
861
|
+
var castIntoOpenaiFunctionTool = (input) => ({
|
|
862
|
+
type: "function",
|
|
863
|
+
name: asOpenaiFunctionName(input.definition.slug),
|
|
864
|
+
description: input.definition.description,
|
|
865
|
+
parameters: asJsonSchema({ schema: input.definition.schema.input }),
|
|
866
|
+
strict: true
|
|
867
|
+
});
|
|
868
|
+
|
|
805
869
|
// src/domain.operations/atoms/genBrainAtom.ts
|
|
870
|
+
var reconstructAssistantItems = (exchangeOutput) => {
|
|
871
|
+
try {
|
|
872
|
+
const parsed = JSON.parse(exchangeOutput);
|
|
873
|
+
if (Array.isArray(parsed)) {
|
|
874
|
+
const hasResponseItems = parsed.some(
|
|
875
|
+
(item) => item?.type === "function_call" || item?.type === "message" || item?.type === "reasoning"
|
|
876
|
+
);
|
|
877
|
+
if (hasResponseItems) {
|
|
878
|
+
return parsed;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
} catch (error) {
|
|
882
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
883
|
+
}
|
|
884
|
+
return [{ role: "assistant", content: exchangeOutput }];
|
|
885
|
+
};
|
|
806
886
|
var genBrainAtom = (input) => {
|
|
807
887
|
const config = CONFIG_BY_ATOM_SLUG[input.slug];
|
|
808
888
|
return new import_brains.BrainAtom({
|
|
@@ -811,32 +891,52 @@ var genBrainAtom = (input) => {
|
|
|
811
891
|
description: config.description,
|
|
812
892
|
spec: config.spec,
|
|
813
893
|
/**
|
|
814
|
-
* .what = stateless inference
|
|
815
|
-
* .why = provides direct model access for reason tasks
|
|
894
|
+
* .what = stateless inference with optional tool use
|
|
895
|
+
* .why = provides direct model access for reason tasks, supports tool invocations
|
|
816
896
|
*/
|
|
817
897
|
ask: async (askInput, context) => {
|
|
818
898
|
const startTime = Date.now();
|
|
819
899
|
const systemPrompt = askInput.role.briefs ? await (0, import_brains.castBriefsToPrompt)({ briefs: askInput.role.briefs }) : void 0;
|
|
820
900
|
const openai = context?.openai ?? new import_openai.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
821
|
-
const jsonSchema =
|
|
901
|
+
const jsonSchema = asJsonSchema({ schema: askInput.schema.output });
|
|
822
902
|
const isObjectSchema = typeof jsonSchema === "object" && jsonSchema !== null && "type" in jsonSchema && jsonSchema.type === "object";
|
|
823
|
-
const
|
|
903
|
+
const isToolExecutionArray = Array.isArray(askInput.prompt);
|
|
904
|
+
const pluggedTools = askInput.plugs?.tools ?? [];
|
|
905
|
+
const hasTools = pluggedTools.length > 0;
|
|
906
|
+
const fullPrompt = isToolExecutionArray ? null : systemPrompt ? `${systemPrompt}
|
|
824
907
|
|
|
825
908
|
---
|
|
826
909
|
|
|
827
910
|
${askInput.prompt}` : askInput.prompt;
|
|
911
|
+
const openaiFunctionTools = hasTools ? pluggedTools.map(
|
|
912
|
+
(definition) => castIntoOpenaiFunctionTool({ definition })
|
|
913
|
+
) : [];
|
|
828
914
|
const priorMessages = askInput.on?.episode?.exchanges.flatMap((exchange) => [
|
|
829
915
|
{ role: "user", content: exchange.input },
|
|
830
|
-
|
|
916
|
+
...reconstructAssistantItems(exchange.output)
|
|
831
917
|
]) ?? [];
|
|
918
|
+
const currentInput = isToolExecutionArray ? askInput.prompt.map(
|
|
919
|
+
(execution) => castIntoOpenaiFunctionCallOutput({ execution })
|
|
920
|
+
) : [{ role: "user", content: fullPrompt }];
|
|
921
|
+
const systemInstruction = hasTools && isObjectSchema ? [
|
|
922
|
+
{
|
|
923
|
+
role: "system",
|
|
924
|
+
content: `When you have the final answer, respond with valid JSON matching this schema:
|
|
925
|
+
${JSON.stringify(jsonSchema, null, 2)}`
|
|
926
|
+
}
|
|
927
|
+
] : [];
|
|
832
928
|
const inputMessages = [
|
|
929
|
+
...systemInstruction,
|
|
833
930
|
...priorMessages,
|
|
834
|
-
|
|
931
|
+
...currentInput
|
|
835
932
|
];
|
|
836
933
|
const response = await openai.responses.create({
|
|
837
934
|
model: config.model,
|
|
838
935
|
input: inputMessages,
|
|
839
|
-
...
|
|
936
|
+
...hasTools && {
|
|
937
|
+
tools: openaiFunctionTools
|
|
938
|
+
},
|
|
939
|
+
...!hasTools && isObjectSchema && {
|
|
840
940
|
text: {
|
|
841
941
|
format: {
|
|
842
942
|
type: "json_schema",
|
|
@@ -847,6 +947,14 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
847
947
|
}
|
|
848
948
|
}
|
|
849
949
|
});
|
|
950
|
+
const functionCallItems = response.output.filter(
|
|
951
|
+
(item) => item.type === "function_call"
|
|
952
|
+
);
|
|
953
|
+
const calls = functionCallItems.length > 0 ? {
|
|
954
|
+
tools: functionCallItems.map(
|
|
955
|
+
(item) => castFromOpenaiFunctionCall({ item })
|
|
956
|
+
)
|
|
957
|
+
} : null;
|
|
850
958
|
const outputItem = response.output.find(
|
|
851
959
|
(item) => item.type === "message"
|
|
852
960
|
);
|
|
@@ -856,7 +964,7 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
856
964
|
const tokensOutput = response.usage?.output_tokens ?? 0;
|
|
857
965
|
const tokensCacheGet = response.usage?.input_tokens_details?.cached_tokens ?? 0;
|
|
858
966
|
const elapsedMs = Date.now() - startTime;
|
|
859
|
-
const charsInput = fullPrompt.length;
|
|
967
|
+
const charsInput = isToolExecutionArray ? JSON.stringify(askInput.prompt).length : fullPrompt.length;
|
|
860
968
|
const charsOutput = content.length;
|
|
861
969
|
const size = {
|
|
862
970
|
tokens: {
|
|
@@ -881,7 +989,38 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
881
989
|
cash
|
|
882
990
|
}
|
|
883
991
|
});
|
|
884
|
-
const output =
|
|
992
|
+
const output = (() => {
|
|
993
|
+
if (!content) return null;
|
|
994
|
+
if (hasTools && isObjectSchema) {
|
|
995
|
+
try {
|
|
996
|
+
return askInput.schema.output.parse(JSON.parse(content));
|
|
997
|
+
} catch (error) {
|
|
998
|
+
const isParseError = error instanceof SyntaxError || error?.constructor?.name === "ZodError";
|
|
999
|
+
if (!isParseError) throw error;
|
|
1000
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
1001
|
+
if (jsonMatch) {
|
|
1002
|
+
try {
|
|
1003
|
+
return askInput.schema.output.parse(JSON.parse(jsonMatch[0]));
|
|
1004
|
+
} catch (extractError) {
|
|
1005
|
+
const isExtractParseError = extractError instanceof SyntaxError || extractError?.constructor?.name === "ZodError";
|
|
1006
|
+
if (!isExtractParseError) throw extractError;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (calls) return null;
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
return isObjectSchema ? askInput.schema.output.parse(JSON.parse(content)) : askInput.schema.output.parse(content);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
const isParseError = error instanceof SyntaxError || error?.constructor?.name === "ZodError";
|
|
1017
|
+
if (!isParseError) throw error;
|
|
1018
|
+
if (calls) return null;
|
|
1019
|
+
throw new Error("structured output parse failed with no tool calls");
|
|
1020
|
+
}
|
|
1021
|
+
})();
|
|
1022
|
+
const exchangeInput = isToolExecutionArray ? JSON.stringify(askInput.prompt) : askInput.prompt;
|
|
1023
|
+
const exchangeOutput = functionCallItems.length > 0 ? JSON.stringify(response.output) : content;
|
|
885
1024
|
const continuables = await (0, import_brains.genBrainContinuables)({
|
|
886
1025
|
for: { grain: "atom" },
|
|
887
1026
|
on: {
|
|
@@ -890,14 +1029,15 @@ ${askInput.prompt}` : askInput.prompt;
|
|
|
890
1029
|
},
|
|
891
1030
|
with: {
|
|
892
1031
|
exchange: {
|
|
893
|
-
input:
|
|
894
|
-
output:
|
|
1032
|
+
input: exchangeInput,
|
|
1033
|
+
output: exchangeOutput,
|
|
895
1034
|
exid: response.id
|
|
896
1035
|
}
|
|
897
1036
|
}
|
|
898
1037
|
});
|
|
899
1038
|
return new import_brains.BrainOutput({
|
|
900
1039
|
output,
|
|
1040
|
+
calls,
|
|
901
1041
|
metrics,
|
|
902
1042
|
episode: continuables.episode,
|
|
903
1043
|
series: continuables.series
|
|
@@ -1264,14 +1404,6 @@ var Codex = class {
|
|
|
1264
1404
|
var import_helpful_errors = require("helpful-errors");
|
|
1265
1405
|
var import_brains2 = require("rhachet/brains");
|
|
1266
1406
|
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
1407
|
var CONFIG_BY_REPL_SLUG = {
|
|
1276
1408
|
// default
|
|
1277
1409
|
"openai/codex": CONFIG_BY_ATOM_SLUG["openai/gpt/codex/5.1-max"],
|
|
@@ -1384,6 +1516,7 @@ var invokeCodex = async (input) => {
|
|
|
1384
1516
|
});
|
|
1385
1517
|
return new import_brains2.BrainOutput({
|
|
1386
1518
|
output,
|
|
1519
|
+
calls: null,
|
|
1387
1520
|
metrics,
|
|
1388
1521
|
episode: continuables.episode,
|
|
1389
1522
|
series: continuables.series
|
|
@@ -1396,11 +1529,10 @@ var genBrainRepl = (input) => {
|
|
|
1396
1529
|
slug: input.slug,
|
|
1397
1530
|
description: config.description,
|
|
1398
1531
|
spec: config.spec,
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
ask: async (askInput, _context) => invokeCodex({
|
|
1532
|
+
// note: repls only accept string prompts (tool execution handled internally by codex SDK)
|
|
1533
|
+
// type assertions needed because BrainRepl contract now supports AsBrainPromptFor<TPlugs>
|
|
1534
|
+
// but codex SDK's thread.run() only accepts string prompts
|
|
1535
|
+
ask: (async (askInput, _context) => invokeCodex({
|
|
1404
1536
|
mode: "ask",
|
|
1405
1537
|
model: config.model,
|
|
1406
1538
|
spec: config.spec,
|
|
@@ -1409,12 +1541,8 @@ var genBrainRepl = (input) => {
|
|
|
1409
1541
|
role: askInput.role,
|
|
1410
1542
|
prompt: askInput.prompt,
|
|
1411
1543
|
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({
|
|
1544
|
+
})),
|
|
1545
|
+
act: (async (actInput, _context) => invokeCodex({
|
|
1418
1546
|
mode: "act",
|
|
1419
1547
|
model: config.model,
|
|
1420
1548
|
spec: config.spec,
|
|
@@ -1423,7 +1551,7 @@ var genBrainRepl = (input) => {
|
|
|
1423
1551
|
role: actInput.role,
|
|
1424
1552
|
prompt: actInput.prompt,
|
|
1425
1553
|
schema: actInput.schema
|
|
1426
|
-
})
|
|
1554
|
+
}))
|
|
1427
1555
|
});
|
|
1428
1556
|
};
|
|
1429
1557
|
|
|
@@ -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;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
/**
|
|
3
3
|
* .what = convert a zod schema to JSON schema for native SDK enforcement
|
|
4
|
-
* .why = enables native structured output support in SDKs
|
|
4
|
+
* .why = enables native structured output support in SDKs to reduce
|
|
5
5
|
* token waste on validation retries
|
|
6
6
|
*
|
|
7
7
|
* .note = different SDKs require different conversion options:
|
|
8
8
|
* - claude-agent-sdk: { $refStrategy: 'root' }
|
|
9
9
|
* - codex-sdk: { target: 'openAi' }
|
|
10
|
+
*
|
|
11
|
+
* .note = openai strict mode requires all properties in required array
|
|
12
|
+
* with optional properties marked as nullable types
|
|
10
13
|
*/
|
|
11
14
|
export declare const asJsonSchema: (input: {
|
|
12
15
|
schema: z.ZodSchema;
|
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.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 --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.
|
|
95
|
-
"rhachet-roles-bhrain": "0.
|
|
96
|
-
"rhachet-roles-bhuild": "0.
|
|
97
|
-
"rhachet-roles-ehmpathy": "1.
|
|
93
|
+
"rhachet": "1.37.18",
|
|
94
|
+
"rhachet-brains-anthropic": "0.4.0",
|
|
95
|
+
"rhachet-roles-bhrain": "0.20.2",
|
|
96
|
+
"rhachet-roles-bhuild": "0.14.4",
|
|
97
|
+
"rhachet-roles-ehmpathy": "1.29.2",
|
|
98
98
|
"test-fns": "1.10.0",
|
|
99
99
|
"tsc-alias": "1.8.10",
|
|
100
100
|
"tsx": "4.20.6",
|