substrate-ai 0.17.0 → 0.17.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.
|
@@ -4046,7 +4046,7 @@ minimatch.unescape = unescape;
|
|
|
4046
4046
|
|
|
4047
4047
|
//#endregion
|
|
4048
4048
|
//#region src/modules/repo-map/query.ts
|
|
4049
|
-
const DEFAULT_MAX_TOKENS = 2e3;
|
|
4049
|
+
const DEFAULT_MAX_TOKENS$1 = 2e3;
|
|
4050
4050
|
/** Chars per token heuristic (same as ContextInjector) */
|
|
4051
4051
|
const CHARS_PER_TOKEN = 4;
|
|
4052
4052
|
/** Overhead chars per symbol line: `:lineNumber symbolType \n` */
|
|
@@ -4062,7 +4062,7 @@ var RepoMapQueryEngine = class {
|
|
|
4062
4062
|
}
|
|
4063
4063
|
async query(q) {
|
|
4064
4064
|
const start = Date.now();
|
|
4065
|
-
const maxTokens = q.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
4065
|
+
const maxTokens = q.maxTokens ?? DEFAULT_MAX_TOKENS$1;
|
|
4066
4066
|
const hasFilesFilter = (q.files?.length ?? 0) > 0;
|
|
4067
4067
|
const hasSymbolsFilter = (q.symbols?.length ?? 0) > 0;
|
|
4068
4068
|
const hasTypesFilter = (q.types?.length ?? 0) > 0;
|
|
@@ -4912,9 +4912,9 @@ const coerceToString = z.preprocess((val) => {
|
|
|
4912
4912
|
*/
|
|
4913
4913
|
const DevStoryResultSchema = z.object({
|
|
4914
4914
|
result: z.preprocess((val) => val === "failure" ? "failed" : val, z.enum(["success", "failed"])),
|
|
4915
|
-
ac_met: z.array(coerceToString),
|
|
4916
|
-
ac_failures: z.array(coerceToString),
|
|
4917
|
-
files_modified: z.array(z.string()),
|
|
4915
|
+
ac_met: z.array(coerceToString).default([]),
|
|
4916
|
+
ac_failures: z.array(coerceToString).default([]),
|
|
4917
|
+
files_modified: z.array(z.string()).default([]),
|
|
4918
4918
|
tests: z.preprocess((val) => {
|
|
4919
4919
|
if (typeof val === "string") {
|
|
4920
4920
|
const lower = val.toLowerCase();
|
|
@@ -4936,7 +4936,11 @@ const DevStoryResultSchema = z.object({
|
|
|
4936
4936
|
* as strings (`"42"` instead of `42`). This handles the conversion.
|
|
4937
4937
|
*/
|
|
4938
4938
|
const coerceOptionalNumber = z.preprocess((val) => typeof val === "string" ? Number(val) : val, z.number().optional());
|
|
4939
|
-
const coerceNumber = z.preprocess((val) =>
|
|
4939
|
+
const coerceNumber = z.preprocess((val) => {
|
|
4940
|
+
if (typeof val === "string") return Number(val);
|
|
4941
|
+
if (Array.isArray(val)) return val.length;
|
|
4942
|
+
return val;
|
|
4943
|
+
}, z.number());
|
|
4940
4944
|
/**
|
|
4941
4945
|
* Schema for a single issue in the code review output.
|
|
4942
4946
|
*/
|
|
@@ -4956,13 +4960,13 @@ const CodeReviewIssueSchema = z.object({
|
|
|
4956
4960
|
* status determination backed by concrete evidence from the diff.
|
|
4957
4961
|
*/
|
|
4958
4962
|
const AcChecklistEntrySchema = z.object({
|
|
4959
|
-
ac_id: z.string(),
|
|
4963
|
+
ac_id: z.string().default(""),
|
|
4960
4964
|
status: z.enum([
|
|
4961
4965
|
"met",
|
|
4962
4966
|
"not_met",
|
|
4963
4967
|
"partial"
|
|
4964
|
-
]),
|
|
4965
|
-
evidence: z.string()
|
|
4968
|
+
]).default("met"),
|
|
4969
|
+
evidence: z.string().default("")
|
|
4966
4970
|
});
|
|
4967
4971
|
/**
|
|
4968
4972
|
* Compute the verdict from the issue list using deterministic rules.
|
|
@@ -28737,228 +28741,2440 @@ const ajv = new import_ajv.default({
|
|
|
28737
28741
|
allErrors: true,
|
|
28738
28742
|
coerceTypes: false
|
|
28739
28743
|
});
|
|
28740
|
-
|
|
28741
|
-
//#endregion
|
|
28742
|
-
//#region packages/factory/dist/handlers/start.js
|
|
28743
28744
|
/**
|
|
28744
|
-
*
|
|
28745
|
-
* No side effects — edge selection drives the next step.
|
|
28746
|
-
*
|
|
28747
|
-
* Story 42-9.
|
|
28745
|
+
* Registry for tool definitions. Provides lookup, validation, and execution.
|
|
28748
28746
|
*/
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28747
|
+
var ToolRegistry = class {
|
|
28748
|
+
_tools = new Map();
|
|
28749
|
+
register(tool) {
|
|
28750
|
+
this._tools.set(tool.name, tool);
|
|
28751
|
+
}
|
|
28752
|
+
async execute(name, args, env) {
|
|
28753
|
+
const tool = this._tools.get(name);
|
|
28754
|
+
if (!tool) return {
|
|
28755
|
+
content: `Unknown tool: ${name}`,
|
|
28756
|
+
isError: true
|
|
28757
|
+
};
|
|
28758
|
+
const validate$1 = ajv.compile(tool.inputSchema);
|
|
28759
|
+
const valid = validate$1(args);
|
|
28760
|
+
if (!valid) {
|
|
28761
|
+
const errors = validate$1.errors ?? [];
|
|
28762
|
+
const messages = errors.map((e) => {
|
|
28763
|
+
const path$2 = e.instancePath ?? e.dataPath ?? "";
|
|
28764
|
+
return `${path$2} ${e.message ?? ""}`.trim();
|
|
28765
|
+
}).join(", ");
|
|
28766
|
+
return {
|
|
28767
|
+
content: `Validation failed for tool '${name}': ${messages}`,
|
|
28768
|
+
isError: true
|
|
28769
|
+
};
|
|
28770
|
+
}
|
|
28771
|
+
try {
|
|
28772
|
+
const result = await tool.executor(args, env);
|
|
28773
|
+
const content = tool.outputTruncation !== void 0 && result.length > tool.outputTruncation ? result.slice(0, tool.outputTruncation) + "\n[truncated]" : result;
|
|
28774
|
+
return {
|
|
28775
|
+
content,
|
|
28776
|
+
isError: false
|
|
28777
|
+
};
|
|
28778
|
+
} catch (err) {
|
|
28779
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28780
|
+
return {
|
|
28781
|
+
content: message,
|
|
28782
|
+
isError: true
|
|
28783
|
+
};
|
|
28784
|
+
}
|
|
28785
|
+
}
|
|
28786
|
+
get(name) {
|
|
28787
|
+
return this._tools.get(name);
|
|
28788
|
+
}
|
|
28789
|
+
getDefinitions() {
|
|
28790
|
+
return Array.from(this._tools.values());
|
|
28791
|
+
}
|
|
28752
28792
|
};
|
|
28753
28793
|
|
|
28754
28794
|
//#endregion
|
|
28755
|
-
//#region packages/factory/dist/
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28795
|
+
//#region packages/factory/dist/agent/types.js
|
|
28796
|
+
const DEFAULT_SESSION_CONFIG = {
|
|
28797
|
+
max_turns: 0,
|
|
28798
|
+
max_tool_rounds_per_input: 0,
|
|
28799
|
+
default_command_timeout_ms: 1e4,
|
|
28800
|
+
max_command_timeout_ms: 6e5,
|
|
28801
|
+
reasoning_effort: null,
|
|
28802
|
+
tool_output_limits: new Map(),
|
|
28803
|
+
enable_loop_detection: true,
|
|
28804
|
+
loop_detection_window: 10,
|
|
28805
|
+
truncation_mode: "head_tail",
|
|
28806
|
+
max_output_lines: 500
|
|
28807
|
+
};
|
|
28808
|
+
const SessionState = {
|
|
28809
|
+
IDLE: "IDLE",
|
|
28810
|
+
PROCESSING: "PROCESSING",
|
|
28811
|
+
AWAITING_INPUT: "AWAITING_INPUT",
|
|
28812
|
+
CLOSED: "CLOSED"
|
|
28813
|
+
};
|
|
28814
|
+
const EventKind = {
|
|
28815
|
+
SESSION_START: "SESSION_START",
|
|
28816
|
+
SESSION_END: "SESSION_END",
|
|
28817
|
+
USER_INPUT: "USER_INPUT",
|
|
28818
|
+
PROCESSING_END: "PROCESSING_END",
|
|
28819
|
+
ASSISTANT_TEXT_START: "ASSISTANT_TEXT_START",
|
|
28820
|
+
ASSISTANT_TEXT_DELTA: "ASSISTANT_TEXT_DELTA",
|
|
28821
|
+
ASSISTANT_TEXT_END: "ASSISTANT_TEXT_END",
|
|
28822
|
+
TOOL_CALL_START: "TOOL_CALL_START",
|
|
28823
|
+
TOOL_CALL_OUTPUT_DELTA: "TOOL_CALL_OUTPUT_DELTA",
|
|
28824
|
+
TOOL_CALL_END: "TOOL_CALL_END",
|
|
28825
|
+
STEERING_INJECTED: "STEERING_INJECTED",
|
|
28826
|
+
TURN_LIMIT: "TURN_LIMIT",
|
|
28827
|
+
LOOP_DETECTION: "LOOP_DETECTION",
|
|
28828
|
+
WARNING: "WARNING",
|
|
28829
|
+
ERROR: "ERROR"
|
|
28765
28830
|
};
|
|
28766
28831
|
|
|
28767
28832
|
//#endregion
|
|
28768
|
-
//#region packages/factory/dist/
|
|
28833
|
+
//#region packages/factory/dist/agent/truncation.js
|
|
28769
28834
|
/**
|
|
28770
|
-
*
|
|
28771
|
-
*
|
|
28772
|
-
* This handler does nothing — it simply returns SUCCESS so the executor
|
|
28773
|
-
* can proceed to edge evaluation.
|
|
28835
|
+
* Default per-tool character output limits.
|
|
28836
|
+
* Tools not listed here fall back to DEFAULT_FALLBACK_CHAR_LIMIT (10,000).
|
|
28774
28837
|
*
|
|
28775
|
-
*
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
* Handler for conditional/branching nodes; routing is delegated entirely to
|
|
28779
|
-
* edge selection (story 42-12); this handler does nothing.
|
|
28838
|
+
* Note: shell is 30K (not 10K as in the AC) — shell output often contains build logs,
|
|
28839
|
+
* test results, and compiler errors that need more context for effective debugging.
|
|
28840
|
+
* The 10K AC value was based on interactive shell use; 30K reflects agentic workloads.
|
|
28780
28841
|
*/
|
|
28781
|
-
const
|
|
28782
|
-
|
|
28842
|
+
const DEFAULT_TOOL_LIMITS = {
|
|
28843
|
+
read_file: 5e4,
|
|
28844
|
+
shell: 3e4,
|
|
28845
|
+
grep: 2e4,
|
|
28846
|
+
glob: 2e4
|
|
28783
28847
|
};
|
|
28848
|
+
/** Fallback character limit for tools not in DEFAULT_TOOL_LIMITS */
|
|
28849
|
+
const DEFAULT_FALLBACK_CHAR_LIMIT = 1e4;
|
|
28850
|
+
/** Maximum lines before Phase 2 line-based truncation is applied */
|
|
28851
|
+
const DEFAULT_LINE_LIMIT = 500;
|
|
28852
|
+
function truncateByChars(output, limit, mode) {
|
|
28853
|
+
if (output.length <= limit) return output;
|
|
28854
|
+
if (mode === "tail") return output.slice(-limit);
|
|
28855
|
+
const half = Math.floor(limit / 2);
|
|
28856
|
+
const removed = output.length - limit;
|
|
28857
|
+
return output.slice(0, half) + `\n\n[... ${removed} characters truncated from middle. Full output available in event stream.]\n\n` + output.slice(-half);
|
|
28858
|
+
}
|
|
28859
|
+
function truncateByLines(output, maxLines, mode) {
|
|
28860
|
+
const lines = output.split("\n");
|
|
28861
|
+
if (lines.length <= maxLines) return output;
|
|
28862
|
+
if (mode === "tail") return lines.slice(-maxLines).join("\n");
|
|
28863
|
+
const headCount = Math.ceil(maxLines / 2);
|
|
28864
|
+
const tailCount = Math.floor(maxLines / 2);
|
|
28865
|
+
const removed = lines.length - maxLines;
|
|
28866
|
+
return lines.slice(0, headCount).join("\n") + `\n[... ${removed} lines truncated from middle ...]\n` + lines.slice(-tailCount).join("\n");
|
|
28867
|
+
}
|
|
28868
|
+
/**
|
|
28869
|
+
* Truncates tool output to fit within LLM context windows using a two-phase pipeline.
|
|
28870
|
+
*
|
|
28871
|
+
* Phase 1 (character-based): Applies per-tool character limits in head_tail or tail mode.
|
|
28872
|
+
* Phase 2 (line-based): Applies max_output_lines limit in head_tail or tail mode.
|
|
28873
|
+
*
|
|
28874
|
+
* If output is within both limits, returns the original string unchanged.
|
|
28875
|
+
*/
|
|
28876
|
+
function truncateToolOutput(output, toolName, config) {
|
|
28877
|
+
const charLimit = config.tool_output_limits.get(toolName) ?? DEFAULT_TOOL_LIMITS[toolName] ?? DEFAULT_FALLBACK_CHAR_LIMIT;
|
|
28878
|
+
const mode = config.truncation_mode ?? "head_tail";
|
|
28879
|
+
const maxLines = config.max_output_lines ?? DEFAULT_LINE_LIMIT;
|
|
28880
|
+
const afterPhase1 = truncateByChars(output, charLimit, mode);
|
|
28881
|
+
return truncateByLines(afterPhase1, maxLines, mode);
|
|
28882
|
+
}
|
|
28784
28883
|
|
|
28785
28884
|
//#endregion
|
|
28786
|
-
//#region packages/factory/dist/
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
*
|
|
28795
|
-
*
|
|
28796
|
-
*
|
|
28797
|
-
|
|
28798
|
-
|
|
28799
|
-
|
|
28800
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
*
|
|
28808
|
-
|
|
28809
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
|
|
28824
|
-
|
|
28825
|
-
|
|
28885
|
+
//#region packages/factory/dist/agent/loop-detection.js
|
|
28886
|
+
var LoopDetector = class {
|
|
28887
|
+
_config;
|
|
28888
|
+
_window = [];
|
|
28889
|
+
constructor(config) {
|
|
28890
|
+
this._config = config;
|
|
28891
|
+
}
|
|
28892
|
+
/**
|
|
28893
|
+
* Record a tool call and check for a repeating pattern.
|
|
28894
|
+
* Returns true if a loop is detected (window is full and a repeating pattern found).
|
|
28895
|
+
* Returns false if detection is disabled, window is not yet full, or no pattern is found.
|
|
28896
|
+
*/
|
|
28897
|
+
record(toolName, toolArgs) {
|
|
28898
|
+
if (!this._config.enabled) return false;
|
|
28899
|
+
const sig = createHash("sha256").update(`${toolName}:${JSON.stringify(toolArgs)}`).digest("hex");
|
|
28900
|
+
this._window.push(sig);
|
|
28901
|
+
if (this._window.length > this._config.windowSize) this._window.shift();
|
|
28902
|
+
if (this._window.length < this._config.windowSize) return false;
|
|
28903
|
+
return this._detectPattern();
|
|
28904
|
+
}
|
|
28905
|
+
/**
|
|
28906
|
+
* Check whether the current window contains a repeating pattern of length 1, 2, or 3.
|
|
28907
|
+
* Pattern length N is only checked if windowSize % N === 0.
|
|
28908
|
+
*/
|
|
28909
|
+
_detectPattern() {
|
|
28910
|
+
const w = this._window;
|
|
28911
|
+
const n$1 = this._config.windowSize;
|
|
28912
|
+
for (const patternLen of [
|
|
28913
|
+
1,
|
|
28914
|
+
2,
|
|
28915
|
+
3
|
|
28916
|
+
]) {
|
|
28917
|
+
if (n$1 % patternLen !== 0) continue;
|
|
28918
|
+
const pattern = w.slice(0, patternLen);
|
|
28919
|
+
let allMatch = true;
|
|
28920
|
+
for (let i = patternLen; i < n$1; i += patternLen) {
|
|
28921
|
+
for (let j$1 = 0; j$1 < patternLen; j$1++) if (w[i + j$1] !== pattern[j$1]) {
|
|
28922
|
+
allMatch = false;
|
|
28923
|
+
break;
|
|
28924
|
+
}
|
|
28925
|
+
if (!allMatch) break;
|
|
28926
|
+
}
|
|
28927
|
+
if (allMatch) return true;
|
|
28826
28928
|
}
|
|
28827
|
-
|
|
28828
|
-
default: return false;
|
|
28929
|
+
return false;
|
|
28829
28930
|
}
|
|
28830
|
-
}
|
|
28831
|
-
/**
|
|
28832
|
-
* Resolve LLM routing properties for a single graph node using the parsed stylesheet.
|
|
28833
|
-
*
|
|
28834
|
-
* @param node - The graph node to resolve properties for.
|
|
28835
|
-
* @param stylesheet - A parsed stylesheet (array of `StylesheetRule` in source order).
|
|
28836
|
-
* @returns A `ResolvedNodeStyles` object containing only the properties that
|
|
28837
|
-
* were resolved from matching rules. Properties absent from matching rules
|
|
28838
|
-
* are omitted (not set to `undefined` explicitly).
|
|
28839
|
-
*
|
|
28840
|
-
* **Important**: this function does NOT check the node's own attributes.
|
|
28841
|
-
* Callers must give explicit node attributes (`node.llmModel`, etc.) priority
|
|
28842
|
-
* over the values returned here.
|
|
28843
|
-
*/
|
|
28844
|
-
function resolveNodeStyles(node, stylesheet) {
|
|
28845
|
-
const matchingRules = stylesheet.filter((rule) => matchesNode(node, rule.selector));
|
|
28846
|
-
matchingRules.sort((a, b) => a.selector.specificity - b.selector.specificity);
|
|
28847
|
-
const resolved = {};
|
|
28848
|
-
for (const rule of matchingRules) for (const decl of rule.declarations) if (decl.property === "llm_model") resolved.llmModel = decl.value;
|
|
28849
|
-
else if (decl.property === "llm_provider") resolved.llmProvider = decl.value;
|
|
28850
|
-
else if (decl.property === "reasoning_effort") resolved.reasoningEffort = decl.value;
|
|
28851
|
-
return resolved;
|
|
28852
|
-
}
|
|
28931
|
+
};
|
|
28853
28932
|
|
|
28854
28933
|
//#endregion
|
|
28855
|
-
//#region packages/factory/dist/
|
|
28856
|
-
|
|
28857
|
-
|
|
28858
|
-
const
|
|
28934
|
+
//#region packages/factory/dist/agent/loop.js
|
|
28935
|
+
/** Build a ToolRegistry from a ProviderProfile's tool definitions. */
|
|
28936
|
+
function buildRegistryFromProfile(profile) {
|
|
28937
|
+
const registry = new ToolRegistry();
|
|
28938
|
+
for (const tool of profile.tools()) registry.register(tool);
|
|
28939
|
+
return registry;
|
|
28940
|
+
}
|
|
28859
28941
|
/**
|
|
28860
|
-
*
|
|
28861
|
-
*
|
|
28862
|
-
|
|
28863
|
-
|
|
28864
|
-
|
|
28865
|
-
|
|
28866
|
-
|
|
28867
|
-
|
|
28868
|
-
|
|
28869
|
-
|
|
28870
|
-
|
|
28871
|
-
|
|
28942
|
+
* Convert session history turns to LLMMessage array for the LLM request.
|
|
28943
|
+
* SystemTurns are skipped (system prompt is provided separately via ProviderProfile).
|
|
28944
|
+
*/
|
|
28945
|
+
function convertHistoryToMessages(history) {
|
|
28946
|
+
const messages = [];
|
|
28947
|
+
for (const turn of history) switch (turn.type) {
|
|
28948
|
+
case "user":
|
|
28949
|
+
case "steering":
|
|
28950
|
+
messages.push({
|
|
28951
|
+
role: "user",
|
|
28952
|
+
content: [{
|
|
28953
|
+
kind: "text",
|
|
28954
|
+
text: turn.content
|
|
28955
|
+
}]
|
|
28956
|
+
});
|
|
28957
|
+
break;
|
|
28958
|
+
case "assistant":
|
|
28959
|
+
if (turn.tool_calls.length > 0) {
|
|
28960
|
+
const parts = [];
|
|
28961
|
+
if (turn.content) parts.push({
|
|
28962
|
+
kind: "text",
|
|
28963
|
+
text: turn.content
|
|
28964
|
+
});
|
|
28965
|
+
for (const tc of turn.tool_calls) parts.push({
|
|
28966
|
+
kind: "tool_call",
|
|
28967
|
+
toolCall: {
|
|
28968
|
+
id: tc.id,
|
|
28969
|
+
name: tc.name,
|
|
28970
|
+
arguments: tc.arguments,
|
|
28971
|
+
...tc.rawArguments !== void 0 ? { rawArguments: tc.rawArguments } : {}
|
|
28972
|
+
}
|
|
28973
|
+
});
|
|
28974
|
+
messages.push({
|
|
28975
|
+
role: "assistant",
|
|
28976
|
+
content: parts
|
|
28977
|
+
});
|
|
28978
|
+
} else messages.push({
|
|
28979
|
+
role: "assistant",
|
|
28980
|
+
content: [{
|
|
28981
|
+
kind: "text",
|
|
28982
|
+
text: turn.content
|
|
28983
|
+
}]
|
|
28984
|
+
});
|
|
28985
|
+
break;
|
|
28986
|
+
case "tool_results":
|
|
28987
|
+
messages.push({
|
|
28988
|
+
role: "user",
|
|
28989
|
+
content: turn.results.map((r) => ({
|
|
28990
|
+
kind: "tool_result",
|
|
28991
|
+
toolResult: {
|
|
28992
|
+
toolCallId: r.tool_call_id,
|
|
28993
|
+
content: r.content,
|
|
28994
|
+
isError: r.is_error
|
|
28995
|
+
}
|
|
28996
|
+
}))
|
|
28997
|
+
});
|
|
28998
|
+
break;
|
|
28999
|
+
case "system": break;
|
|
29000
|
+
}
|
|
29001
|
+
return messages;
|
|
28872
29002
|
}
|
|
28873
29003
|
/**
|
|
28874
|
-
*
|
|
28875
|
-
*
|
|
28876
|
-
* Priority (highest wins):
|
|
28877
|
-
* 1. Node-level attributes (`node.llmModel`, `node.llmProvider`, `node.reasoningEffort`)
|
|
28878
|
-
* 2. Stylesheet-resolved values via `resolveNodeStyles`
|
|
28879
|
-
* 3. Per-option defaults (`options.defaultModel`, etc.)
|
|
28880
|
-
* 4. System defaults (`claude-sonnet-4-5`, `anthropic`, `medium`)
|
|
29004
|
+
* Build LLMRequest from the current session state.
|
|
28881
29005
|
*/
|
|
28882
|
-
function
|
|
28883
|
-
const
|
|
28884
|
-
const
|
|
28885
|
-
const
|
|
28886
|
-
|
|
28887
|
-
|
|
28888
|
-
|
|
28889
|
-
|
|
28890
|
-
|
|
29006
|
+
function buildLLMRequest(session) {
|
|
29007
|
+
const profile = session.providerProfile;
|
|
29008
|
+
const config = session.config;
|
|
29009
|
+
const tools = profile.tools().map((t) => ({
|
|
29010
|
+
name: t.name,
|
|
29011
|
+
description: t.description,
|
|
29012
|
+
parameters: t.inputSchema
|
|
29013
|
+
}));
|
|
29014
|
+
const providerOptions = profile.provider_options();
|
|
29015
|
+
const { max_tokens, temperature,...rest } = providerOptions;
|
|
29016
|
+
const req = {
|
|
29017
|
+
model: profile.model,
|
|
29018
|
+
systemPrompt: profile.build_system_prompt(),
|
|
29019
|
+
messages: convertHistoryToMessages(session.history),
|
|
29020
|
+
tools,
|
|
29021
|
+
toolChoice: "auto"
|
|
28891
29022
|
};
|
|
29023
|
+
if (max_tokens !== void 0) req.maxTokens = max_tokens;
|
|
29024
|
+
if (temperature !== void 0) req.temperature = temperature;
|
|
29025
|
+
if (config.reasoning_effort) req.reasoningEffort = config.reasoning_effort;
|
|
29026
|
+
if (Object.keys(rest).length > 0) req.extra = rest;
|
|
29027
|
+
return req;
|
|
28892
29028
|
}
|
|
28893
|
-
|
|
28894
|
-
|
|
28895
|
-
|
|
28896
|
-
|
|
28897
|
-
|
|
28898
|
-
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
|
|
28908
|
-
|
|
29029
|
+
var CodingAgentSession = class {
|
|
29030
|
+
id;
|
|
29031
|
+
state;
|
|
29032
|
+
history;
|
|
29033
|
+
config;
|
|
29034
|
+
llmClient;
|
|
29035
|
+
providerProfile;
|
|
29036
|
+
executionEnv;
|
|
29037
|
+
_steeringQueue;
|
|
29038
|
+
_followupQueue;
|
|
29039
|
+
_emitter;
|
|
29040
|
+
_abortController;
|
|
29041
|
+
_toolRegistry;
|
|
29042
|
+
constructor(options) {
|
|
29043
|
+
this.id = randomUUID();
|
|
29044
|
+
this.state = SessionState.IDLE;
|
|
29045
|
+
this.history = [];
|
|
29046
|
+
this._steeringQueue = [];
|
|
29047
|
+
this._followupQueue = [];
|
|
29048
|
+
this._emitter = new EventEmitter();
|
|
29049
|
+
this._emitter.setMaxListeners(50);
|
|
29050
|
+
this._abortController = new AbortController();
|
|
29051
|
+
this.llmClient = options.llmClient;
|
|
29052
|
+
this.providerProfile = options.providerProfile;
|
|
29053
|
+
this.executionEnv = options.executionEnv;
|
|
29054
|
+
this.config = {
|
|
29055
|
+
...DEFAULT_SESSION_CONFIG,
|
|
29056
|
+
...options.config,
|
|
29057
|
+
tool_output_limits: options.config?.tool_output_limits ?? new Map(),
|
|
29058
|
+
truncation_mode: options.config?.truncation_mode ?? "head_tail",
|
|
29059
|
+
max_output_lines: options.config?.max_output_lines ?? DEFAULT_LINE_LIMIT
|
|
29060
|
+
};
|
|
29061
|
+
this._toolRegistry = buildRegistryFromProfile(options.providerProfile);
|
|
29062
|
+
this._emit(EventKind.SESSION_START, { session_id: this.id });
|
|
28909
29063
|
}
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
|
|
28915
|
-
|
|
28916
|
-
|
|
28917
|
-
|
|
28918
|
-
|
|
28919
|
-
|
|
28920
|
-
|
|
28921
|
-
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
|
|
28938
|
-
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
28942
|
-
|
|
28943
|
-
|
|
28944
|
-
|
|
28945
|
-
|
|
28946
|
-
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
|
|
28950
|
-
|
|
29064
|
+
/** Subscribe to session events. */
|
|
29065
|
+
on(kind, handler) {
|
|
29066
|
+
this._emitter.on(kind, handler);
|
|
29067
|
+
}
|
|
29068
|
+
/** Transition to CLOSED state and emit SESSION_END. */
|
|
29069
|
+
close() {
|
|
29070
|
+
this.state = SessionState.CLOSED;
|
|
29071
|
+
this._emit(EventKind.SESSION_END, {});
|
|
29072
|
+
}
|
|
29073
|
+
/** Abort the current operation and close the session. */
|
|
29074
|
+
abort() {
|
|
29075
|
+
this._abortController.abort();
|
|
29076
|
+
this.close();
|
|
29077
|
+
}
|
|
29078
|
+
/**
|
|
29079
|
+
* Push a steering message onto the steering queue.
|
|
29080
|
+
* It will be injected into history (as a SteeringTurn) before the next LLM call.
|
|
29081
|
+
* Safe to call from any state (IDLE, PROCESSING, CLOSED); if CLOSED, the message
|
|
29082
|
+
* is queued but will never be drained.
|
|
29083
|
+
*/
|
|
29084
|
+
steer(message) {
|
|
29085
|
+
this._steeringQueue.push(message);
|
|
29086
|
+
}
|
|
29087
|
+
/**
|
|
29088
|
+
* Push a follow-up message onto the follow-up queue.
|
|
29089
|
+
* After the current processInput cycle completes naturally (no more tool calls),
|
|
29090
|
+
* the first queued follow-up triggers a new processInput cycle recursively.
|
|
29091
|
+
* PROCESSING_END is not emitted until all follow-ups are exhausted.
|
|
29092
|
+
* Safe to call from any state.
|
|
29093
|
+
*/
|
|
29094
|
+
follow_up(message) {
|
|
29095
|
+
this._followupQueue.push(message);
|
|
29096
|
+
}
|
|
29097
|
+
/**
|
|
29098
|
+
* Construct and emit a SessionEvent.
|
|
29099
|
+
* Internal — underscore prefix signals non-public API.
|
|
29100
|
+
*/
|
|
29101
|
+
_emit(kind, data = {}) {
|
|
29102
|
+
const event = {
|
|
29103
|
+
kind,
|
|
29104
|
+
timestamp: new Date(),
|
|
29105
|
+
session_id: this.id,
|
|
29106
|
+
data
|
|
29107
|
+
};
|
|
29108
|
+
this._emitter.emit(kind, event);
|
|
29109
|
+
}
|
|
29110
|
+
/**
|
|
29111
|
+
* Dequeue all steering messages, append as SteeringTurns, emit STEERING_INJECTED.
|
|
29112
|
+
* Called before each LLM call and after tool execution.
|
|
29113
|
+
* Story 48-8 will extend this method.
|
|
29114
|
+
*/
|
|
29115
|
+
_drainSteering() {
|
|
29116
|
+
while (this._steeringQueue.length > 0) {
|
|
29117
|
+
const content = this._steeringQueue.shift();
|
|
29118
|
+
const turn = {
|
|
29119
|
+
type: "steering",
|
|
29120
|
+
content,
|
|
29121
|
+
timestamp: new Date()
|
|
28951
29122
|
};
|
|
29123
|
+
this.history.push(turn);
|
|
29124
|
+
this._emit(EventKind.STEERING_INJECTED, { content });
|
|
28952
29125
|
}
|
|
28953
|
-
}
|
|
28954
|
-
|
|
28955
|
-
|
|
28956
|
-
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
*
|
|
28960
|
-
*
|
|
28961
|
-
*
|
|
29126
|
+
}
|
|
29127
|
+
/**
|
|
29128
|
+
* Process a user input string through the agentic loop.
|
|
29129
|
+
*
|
|
29130
|
+
* Appends a UserTurn, then repeatedly calls the LLM and executes tool calls
|
|
29131
|
+
* until natural completion (no tool calls) or a configured limit is hit.
|
|
29132
|
+
*
|
|
29133
|
+
* After natural completion, drains the follow-up queue (FIFO) by recursively
|
|
29134
|
+
* calling processInput for each queued message. PROCESSING_END is only emitted
|
|
29135
|
+
* when the follow-up queue is fully exhausted.
|
|
29136
|
+
*/
|
|
29137
|
+
async processInput(userInput) {
|
|
29138
|
+
this.state = SessionState.PROCESSING;
|
|
29139
|
+
const userTurn = {
|
|
29140
|
+
type: "user",
|
|
29141
|
+
content: userInput,
|
|
29142
|
+
timestamp: new Date()
|
|
29143
|
+
};
|
|
29144
|
+
this.history.push(userTurn);
|
|
29145
|
+
this._emit(EventKind.USER_INPUT, { content: userInput });
|
|
29146
|
+
let roundCount = 0;
|
|
29147
|
+
const loopDetector = new LoopDetector({
|
|
29148
|
+
windowSize: this.config.loop_detection_window,
|
|
29149
|
+
enabled: this.config.enable_loop_detection
|
|
29150
|
+
});
|
|
29151
|
+
try {
|
|
29152
|
+
while (true) {
|
|
29153
|
+
const totalTurns = this.history.length;
|
|
29154
|
+
if (this.config.max_turns > 0 && totalTurns >= this.config.max_turns) {
|
|
29155
|
+
this._emit(EventKind.TURN_LIMIT, {
|
|
29156
|
+
total_turns: totalTurns,
|
|
29157
|
+
reason: "max_turns"
|
|
29158
|
+
});
|
|
29159
|
+
break;
|
|
29160
|
+
}
|
|
29161
|
+
if (this.config.max_tool_rounds_per_input > 0 && roundCount >= this.config.max_tool_rounds_per_input) {
|
|
29162
|
+
this._emit(EventKind.TURN_LIMIT, {
|
|
29163
|
+
round: roundCount,
|
|
29164
|
+
reason: "max_tool_rounds_per_input"
|
|
29165
|
+
});
|
|
29166
|
+
break;
|
|
29167
|
+
}
|
|
29168
|
+
if (this._abortController.signal.aborted) break;
|
|
29169
|
+
this._drainSteering();
|
|
29170
|
+
const response = await this.llmClient.complete(buildLLMRequest(this));
|
|
29171
|
+
const assistantTurn = {
|
|
29172
|
+
type: "assistant",
|
|
29173
|
+
content: response.content,
|
|
29174
|
+
tool_calls: response.toolCalls ?? [],
|
|
29175
|
+
reasoning: null,
|
|
29176
|
+
usage: response.usage,
|
|
29177
|
+
response_id: response.id ?? null,
|
|
29178
|
+
timestamp: new Date()
|
|
29179
|
+
};
|
|
29180
|
+
this.history.push(assistantTurn);
|
|
29181
|
+
this._emit(EventKind.ASSISTANT_TEXT_END, {
|
|
29182
|
+
text: response.content,
|
|
29183
|
+
reasoning: null
|
|
29184
|
+
});
|
|
29185
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
29186
|
+
if (this._followupQueue.length > 0) {
|
|
29187
|
+
const nextInput = this._followupQueue.shift();
|
|
29188
|
+
await this.processInput(nextInput);
|
|
29189
|
+
return;
|
|
29190
|
+
}
|
|
29191
|
+
break;
|
|
29192
|
+
}
|
|
29193
|
+
roundCount++;
|
|
29194
|
+
const toolResults = await this._executeToolCalls(response.toolCalls);
|
|
29195
|
+
const toolResultsTurn = {
|
|
29196
|
+
type: "tool_results",
|
|
29197
|
+
results: toolResults,
|
|
29198
|
+
timestamp: new Date()
|
|
29199
|
+
};
|
|
29200
|
+
this.history.push(toolResultsTurn);
|
|
29201
|
+
let loopTriggered = false;
|
|
29202
|
+
for (const tc of response.toolCalls) {
|
|
29203
|
+
const detected = loopDetector.record(tc.name, tc.arguments);
|
|
29204
|
+
if (detected) loopTriggered = true;
|
|
29205
|
+
}
|
|
29206
|
+
this._drainSteering();
|
|
29207
|
+
if (loopTriggered) {
|
|
29208
|
+
const warningMessage = `Loop detected: the last ${this.config.loop_detection_window} tool calls follow a repeating pattern. Try a different approach.`;
|
|
29209
|
+
const loopTurn = {
|
|
29210
|
+
type: "steering",
|
|
29211
|
+
content: warningMessage,
|
|
29212
|
+
timestamp: new Date()
|
|
29213
|
+
};
|
|
29214
|
+
this.history.push(loopTurn);
|
|
29215
|
+
this._emit(EventKind.LOOP_DETECTION, { message: warningMessage });
|
|
29216
|
+
}
|
|
29217
|
+
}
|
|
29218
|
+
this.state = SessionState.IDLE;
|
|
29219
|
+
this._emit(EventKind.PROCESSING_END, {});
|
|
29220
|
+
} catch (err) {
|
|
29221
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29222
|
+
this._emit(EventKind.ERROR, { message });
|
|
29223
|
+
this.state = SessionState.CLOSED;
|
|
29224
|
+
throw err;
|
|
29225
|
+
}
|
|
29226
|
+
}
|
|
29227
|
+
/** Dispatch tool calls in parallel or sequentially based on provider profile. */
|
|
29228
|
+
async _executeToolCalls(toolCalls) {
|
|
29229
|
+
if (this.providerProfile.supports_parallel_tool_calls && toolCalls.length > 1) return Promise.all(toolCalls.map((tc) => this._executeSingleTool(tc)));
|
|
29230
|
+
else {
|
|
29231
|
+
const results = [];
|
|
29232
|
+
for (const tc of toolCalls) results.push(await this._executeSingleTool(tc));
|
|
29233
|
+
return results;
|
|
29234
|
+
}
|
|
29235
|
+
}
|
|
29236
|
+
/** Execute a single tool call: lookup, emit events, delegate to registry, truncate. */
|
|
29237
|
+
async _executeSingleTool(toolCall) {
|
|
29238
|
+
this._emit(EventKind.TOOL_CALL_START, {
|
|
29239
|
+
tool_name: toolCall.name,
|
|
29240
|
+
call_id: toolCall.id
|
|
29241
|
+
});
|
|
29242
|
+
const toolDef = this._toolRegistry.get(toolCall.name);
|
|
29243
|
+
if (!toolDef) {
|
|
29244
|
+
const errorContent = `Unknown tool: ${toolCall.name}`;
|
|
29245
|
+
this._emit(EventKind.TOOL_CALL_END, {
|
|
29246
|
+
call_id: toolCall.id,
|
|
29247
|
+
output: errorContent,
|
|
29248
|
+
is_error: true
|
|
29249
|
+
});
|
|
29250
|
+
return {
|
|
29251
|
+
tool_call_id: toolCall.id,
|
|
29252
|
+
content: errorContent,
|
|
29253
|
+
is_error: true
|
|
29254
|
+
};
|
|
29255
|
+
}
|
|
29256
|
+
const toolResult = await this._toolRegistry.execute(toolCall.name, toolCall.arguments, this.executionEnv);
|
|
29257
|
+
const rawOutput = toolResult.content;
|
|
29258
|
+
const truncatedOutput = truncateToolOutput(rawOutput, toolCall.name, this.config);
|
|
29259
|
+
this._emit(EventKind.TOOL_CALL_END, {
|
|
29260
|
+
call_id: toolCall.id,
|
|
29261
|
+
output: rawOutput,
|
|
29262
|
+
is_error: toolResult.isError
|
|
29263
|
+
});
|
|
29264
|
+
return {
|
|
29265
|
+
tool_call_id: toolCall.id,
|
|
29266
|
+
content: truncatedOutput,
|
|
29267
|
+
is_error: toolResult.isError
|
|
29268
|
+
};
|
|
29269
|
+
}
|
|
29270
|
+
};
|
|
29271
|
+
/**
|
|
29272
|
+
* Create a new CodingAgentSession.
|
|
29273
|
+
* SESSION_START is emitted synchronously during construction.
|
|
29274
|
+
*/
|
|
29275
|
+
function createSession(options) {
|
|
29276
|
+
return new CodingAgentSession(options);
|
|
29277
|
+
}
|
|
29278
|
+
|
|
29279
|
+
//#endregion
|
|
29280
|
+
//#region packages/factory/dist/backend/direct-backend.js
|
|
29281
|
+
var DirectCodergenBackend = class {
|
|
29282
|
+
options;
|
|
29283
|
+
constructor(options) {
|
|
29284
|
+
this.options = options;
|
|
29285
|
+
}
|
|
29286
|
+
async run(node, prompt, _context) {
|
|
29287
|
+
const { llmClient, providerProfile, executionEnv, config, onEvent } = this.options;
|
|
29288
|
+
const sessionOptions = {
|
|
29289
|
+
llmClient,
|
|
29290
|
+
providerProfile,
|
|
29291
|
+
executionEnv
|
|
29292
|
+
};
|
|
29293
|
+
if (config !== void 0) sessionOptions.config = config;
|
|
29294
|
+
const session = createSession(sessionOptions);
|
|
29295
|
+
let turnLimitHit = false;
|
|
29296
|
+
if (onEvent) for (const kind of Object.values(EventKind)) session.on(kind, onEvent);
|
|
29297
|
+
session.on(EventKind.TURN_LIMIT, () => {
|
|
29298
|
+
turnLimitHit = true;
|
|
29299
|
+
});
|
|
29300
|
+
try {
|
|
29301
|
+
await session.processInput(prompt);
|
|
29302
|
+
} catch (err) {
|
|
29303
|
+
const failureReason = err instanceof Error ? err.message : String(err);
|
|
29304
|
+
return {
|
|
29305
|
+
status: "FAILURE",
|
|
29306
|
+
failureReason
|
|
29307
|
+
};
|
|
29308
|
+
} finally {
|
|
29309
|
+
session.close();
|
|
29310
|
+
}
|
|
29311
|
+
if (turnLimitHit) return {
|
|
29312
|
+
status: "FAILURE",
|
|
29313
|
+
failureReason: "turn limit exceeded"
|
|
29314
|
+
};
|
|
29315
|
+
const finalAssistantTurn = [...session.history].reverse().find((t) => t.type === "assistant");
|
|
29316
|
+
if (!finalAssistantTurn || finalAssistantTurn.type !== "assistant") return { status: "SUCCESS" };
|
|
29317
|
+
return {
|
|
29318
|
+
status: "SUCCESS",
|
|
29319
|
+
contextUpdates: { [`${node.id}_output`]: finalAssistantTurn.content }
|
|
29320
|
+
};
|
|
29321
|
+
}
|
|
29322
|
+
};
|
|
29323
|
+
function createDirectCodergenBackend(options) {
|
|
29324
|
+
return new DirectCodergenBackend(options);
|
|
29325
|
+
}
|
|
29326
|
+
|
|
29327
|
+
//#endregion
|
|
29328
|
+
//#region packages/factory/dist/llm/model-registry.js
|
|
29329
|
+
const DEFAULT_PATTERNS = [
|
|
29330
|
+
{
|
|
29331
|
+
pattern: /^claude-/i,
|
|
29332
|
+
provider: "anthropic"
|
|
29333
|
+
},
|
|
29334
|
+
{
|
|
29335
|
+
pattern: /^gpt-/i,
|
|
29336
|
+
provider: "openai"
|
|
29337
|
+
},
|
|
29338
|
+
{
|
|
29339
|
+
pattern: /^o\d(-|$)/i,
|
|
29340
|
+
provider: "openai"
|
|
29341
|
+
},
|
|
29342
|
+
{
|
|
29343
|
+
pattern: /^gemini-/i,
|
|
29344
|
+
provider: "gemini"
|
|
29345
|
+
}
|
|
29346
|
+
];
|
|
29347
|
+
var ModelRegistry = class {
|
|
29348
|
+
patterns = [...DEFAULT_PATTERNS];
|
|
29349
|
+
/**
|
|
29350
|
+
* Register a glob-style pattern (supports `*` wildcard) mapped to a provider name.
|
|
29351
|
+
* Custom patterns are prepended so they override defaults.
|
|
29352
|
+
*/
|
|
29353
|
+
register(globPattern, provider) {
|
|
29354
|
+
const escaped = globPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
29355
|
+
this.patterns.unshift({
|
|
29356
|
+
pattern: new RegExp(`^${escaped}$`, "i"),
|
|
29357
|
+
provider
|
|
29358
|
+
});
|
|
29359
|
+
}
|
|
29360
|
+
/**
|
|
29361
|
+
* Resolve a model string to a provider name.
|
|
29362
|
+
* Returns `null` if no pattern matches.
|
|
29363
|
+
*/
|
|
29364
|
+
resolve(model) {
|
|
29365
|
+
for (const { pattern, provider } of this.patterns) if (pattern.test(model)) return provider;
|
|
29366
|
+
return null;
|
|
29367
|
+
}
|
|
29368
|
+
};
|
|
29369
|
+
|
|
29370
|
+
//#endregion
|
|
29371
|
+
//#region packages/factory/dist/llm/middleware/types.js
|
|
29372
|
+
/**
|
|
29373
|
+
* Composes middleware into a single next-function.
|
|
29374
|
+
* First middleware in the array is the outermost wrapper (runs first / last to finish).
|
|
29375
|
+
* Implemented via reduceRight so the array order matches onion semantics.
|
|
29376
|
+
*/
|
|
29377
|
+
function buildMiddlewareChain(middleware, base) {
|
|
29378
|
+
return middleware.reduceRight((next, mw) => (req) => mw(req, next), base);
|
|
29379
|
+
}
|
|
29380
|
+
|
|
29381
|
+
//#endregion
|
|
29382
|
+
//#region packages/factory/dist/llm/client.js
|
|
29383
|
+
var LLMClient = class {
|
|
29384
|
+
adapters = new Map();
|
|
29385
|
+
modelRegistry = new ModelRegistry();
|
|
29386
|
+
_middleware = [];
|
|
29387
|
+
constructor(adapters) {
|
|
29388
|
+
if (adapters) for (const [name, adapter] of Object.entries(adapters)) this.registerProvider(name, adapter);
|
|
29389
|
+
}
|
|
29390
|
+
registerProvider(name, adapter) {
|
|
29391
|
+
this.adapters.set(name, adapter);
|
|
29392
|
+
}
|
|
29393
|
+
registerModelPattern(pattern, providerName) {
|
|
29394
|
+
this.modelRegistry.register(pattern, providerName);
|
|
29395
|
+
}
|
|
29396
|
+
/**
|
|
29397
|
+
* Register a middleware function that wraps every `complete()` call.
|
|
29398
|
+
* Returns `this` for chaining.
|
|
29399
|
+
*/
|
|
29400
|
+
use(mw) {
|
|
29401
|
+
this._middleware.push(mw);
|
|
29402
|
+
return this;
|
|
29403
|
+
}
|
|
29404
|
+
resolveAdapter(model) {
|
|
29405
|
+
const providerName = this.modelRegistry.resolve(model);
|
|
29406
|
+
const registered = [...this.adapters.keys()];
|
|
29407
|
+
if (!providerName) throw new Error(`No provider matched model "${model}". Registered providers: ${registered.join(", ") || "(none)"}`);
|
|
29408
|
+
const adapter = this.adapters.get(providerName);
|
|
29409
|
+
if (!adapter) throw new Error(`Provider "${providerName}" matched model "${model}" but is not registered. Registered providers: ${registered.join(", ") || "(none)"}`);
|
|
29410
|
+
return adapter;
|
|
29411
|
+
}
|
|
29412
|
+
async complete(request) {
|
|
29413
|
+
const baseNext = (req) => this.resolveAdapter(req.model).complete(req);
|
|
29414
|
+
const chain = buildMiddlewareChain(this._middleware, baseNext);
|
|
29415
|
+
return chain(request);
|
|
29416
|
+
}
|
|
29417
|
+
async *stream(request) {
|
|
29418
|
+
yield* this.resolveAdapter(request.model).stream(request);
|
|
29419
|
+
}
|
|
29420
|
+
};
|
|
29421
|
+
|
|
29422
|
+
//#endregion
|
|
29423
|
+
//#region packages/factory/dist/llm/types.js
|
|
29424
|
+
/**
|
|
29425
|
+
* Error thrown by provider adapters that includes the HTTP status code.
|
|
29426
|
+
* The retry middleware uses `statusCode` to determine if an error is retryable
|
|
29427
|
+
* (429, 500, 502, 503) vs non-retryable (400, 401, 403, 404).
|
|
29428
|
+
*/
|
|
29429
|
+
var LLMError = class extends Error {
|
|
29430
|
+
statusCode;
|
|
29431
|
+
provider;
|
|
29432
|
+
constructor(message, statusCode, provider) {
|
|
29433
|
+
super(message);
|
|
29434
|
+
this.statusCode = statusCode;
|
|
29435
|
+
this.provider = provider;
|
|
29436
|
+
this.name = "LLMError";
|
|
29437
|
+
}
|
|
29438
|
+
};
|
|
29439
|
+
|
|
29440
|
+
//#endregion
|
|
29441
|
+
//#region packages/factory/dist/llm/providers/anthropic.js
|
|
29442
|
+
const MAX_RETRIES = 3;
|
|
29443
|
+
const BASE_DELAY_MS = 1e3;
|
|
29444
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
29445
|
+
const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
29446
|
+
const PROMPT_CACHING_BETA = "prompt-caching-2024-07-31";
|
|
29447
|
+
function mapStopReason(raw) {
|
|
29448
|
+
switch (raw) {
|
|
29449
|
+
case "end_turn":
|
|
29450
|
+
case "stop_sequence": return "stop";
|
|
29451
|
+
case "max_tokens": return "length";
|
|
29452
|
+
case "tool_use": return "tool_calls";
|
|
29453
|
+
default: return "other";
|
|
29454
|
+
}
|
|
29455
|
+
}
|
|
29456
|
+
function reasoningBudget(effort) {
|
|
29457
|
+
switch (effort) {
|
|
29458
|
+
case "low": return 1024;
|
|
29459
|
+
case "medium": return 8192;
|
|
29460
|
+
case "high": return 32e3;
|
|
29461
|
+
}
|
|
29462
|
+
}
|
|
29463
|
+
var AnthropicAdapter = class {
|
|
29464
|
+
name = "anthropic";
|
|
29465
|
+
apiKey;
|
|
29466
|
+
baseUrl;
|
|
29467
|
+
anthropicVersion;
|
|
29468
|
+
fetch;
|
|
29469
|
+
constructor(options) {
|
|
29470
|
+
this.apiKey = options.apiKey;
|
|
29471
|
+
this.baseUrl = options.baseUrl ?? "https://api.anthropic.com";
|
|
29472
|
+
this.anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
|
|
29473
|
+
this.fetch = options.fetch ?? globalThis.fetch;
|
|
29474
|
+
}
|
|
29475
|
+
async complete(request) {
|
|
29476
|
+
const { body, betaHeaders } = this.buildRequestBody(request);
|
|
29477
|
+
const headers = this.buildHeaders(betaHeaders);
|
|
29478
|
+
const url = `${this.baseUrl}/v1/messages`;
|
|
29479
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
29480
|
+
const response = await this.fetch(url, {
|
|
29481
|
+
method: "POST",
|
|
29482
|
+
headers,
|
|
29483
|
+
body: JSON.stringify(body)
|
|
29484
|
+
});
|
|
29485
|
+
if (response.status === 429) {
|
|
29486
|
+
if (attempt === MAX_RETRIES) throw new LLMError(`[anthropic] Rate limit exceeded after ${MAX_RETRIES} retries`, 429, "anthropic");
|
|
29487
|
+
const retryAfterStr = response.headers.get("Retry-After");
|
|
29488
|
+
const retryAfter = retryAfterStr !== null ? parseInt(retryAfterStr, 10) * 1e3 : 0;
|
|
29489
|
+
const backoff = Math.max(retryAfter, BASE_DELAY_MS * 2 ** attempt);
|
|
29490
|
+
await new Promise((resolve$6) => setTimeout(resolve$6, backoff));
|
|
29491
|
+
continue;
|
|
29492
|
+
}
|
|
29493
|
+
if (!response.ok) {
|
|
29494
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
29495
|
+
const errorObj = errorBody?.error;
|
|
29496
|
+
const message = typeof errorObj?.message === "string" ? errorObj.message : response.statusText;
|
|
29497
|
+
throw new LLMError(`[anthropic] ${response.status}: ${message}`, response.status, "anthropic");
|
|
29498
|
+
}
|
|
29499
|
+
const raw = await response.json();
|
|
29500
|
+
return this.parseResponse(raw, request.model);
|
|
29501
|
+
}
|
|
29502
|
+
throw new Error("[anthropic] Unexpected end of retry loop");
|
|
29503
|
+
}
|
|
29504
|
+
async *stream(_request) {
|
|
29505
|
+
throw new Error("streaming not yet implemented");
|
|
29506
|
+
}
|
|
29507
|
+
buildRequestBody(request) {
|
|
29508
|
+
const betaHeaders = [];
|
|
29509
|
+
const anthropicExtra = request.extra?.anthropic;
|
|
29510
|
+
const autoCache = anthropicExtra !== void 0 ? anthropicExtra["auto_cache"] !== false : true;
|
|
29511
|
+
const extraBetas = anthropicExtra?.["beta_headers"];
|
|
29512
|
+
if (Array.isArray(extraBetas)) for (const b of extraBetas) betaHeaders.push(b);
|
|
29513
|
+
let systemBlocks;
|
|
29514
|
+
if (request.systemPrompt) {
|
|
29515
|
+
const block = {
|
|
29516
|
+
type: "text",
|
|
29517
|
+
text: request.systemPrompt
|
|
29518
|
+
};
|
|
29519
|
+
systemBlocks = [block];
|
|
29520
|
+
}
|
|
29521
|
+
const messages = this.translateMessages(request.messages);
|
|
29522
|
+
const toolChoiceIsNone = request.toolChoice === "none";
|
|
29523
|
+
let tools;
|
|
29524
|
+
let toolChoice;
|
|
29525
|
+
if (!toolChoiceIsNone && request.tools && request.tools.length > 0) {
|
|
29526
|
+
tools = this.translateTools(request.tools);
|
|
29527
|
+
toolChoice = this.mapToolChoice(request.toolChoice);
|
|
29528
|
+
}
|
|
29529
|
+
let cachingInjected = false;
|
|
29530
|
+
if (autoCache) {
|
|
29531
|
+
if (systemBlocks && systemBlocks.length > 0) {
|
|
29532
|
+
const lastBlock = systemBlocks[systemBlocks.length - 1];
|
|
29533
|
+
if (lastBlock && !("cache_control" in lastBlock && lastBlock.cache_control)) {
|
|
29534
|
+
lastBlock.cache_control = { type: "ephemeral" };
|
|
29535
|
+
cachingInjected = true;
|
|
29536
|
+
}
|
|
29537
|
+
}
|
|
29538
|
+
if (tools && tools.length > 0) {
|
|
29539
|
+
const lastTool = tools[tools.length - 1];
|
|
29540
|
+
if (lastTool && !lastTool.cache_control) {
|
|
29541
|
+
lastTool.cache_control = { type: "ephemeral" };
|
|
29542
|
+
cachingInjected = true;
|
|
29543
|
+
}
|
|
29544
|
+
}
|
|
29545
|
+
}
|
|
29546
|
+
if (cachingInjected && !betaHeaders.includes(PROMPT_CACHING_BETA)) betaHeaders.push(PROMPT_CACHING_BETA);
|
|
29547
|
+
const body = {
|
|
29548
|
+
model: request.model,
|
|
29549
|
+
max_tokens: request.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
29550
|
+
messages
|
|
29551
|
+
};
|
|
29552
|
+
if (systemBlocks) body.system = systemBlocks;
|
|
29553
|
+
if (tools) body.tools = tools;
|
|
29554
|
+
if (toolChoice) body.tool_choice = toolChoice;
|
|
29555
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
29556
|
+
if (request.reasoningEffort) body.thinking = {
|
|
29557
|
+
type: "enabled",
|
|
29558
|
+
budget_tokens: reasoningBudget(request.reasoningEffort)
|
|
29559
|
+
};
|
|
29560
|
+
return {
|
|
29561
|
+
body,
|
|
29562
|
+
betaHeaders
|
|
29563
|
+
};
|
|
29564
|
+
}
|
|
29565
|
+
buildHeaders(betaHeaders) {
|
|
29566
|
+
const headers = {
|
|
29567
|
+
"Content-Type": "application/json",
|
|
29568
|
+
"x-api-key": this.apiKey,
|
|
29569
|
+
"anthropic-version": this.anthropicVersion
|
|
29570
|
+
};
|
|
29571
|
+
if (betaHeaders.length > 0) headers["anthropic-beta"] = betaHeaders.join(",");
|
|
29572
|
+
return headers;
|
|
29573
|
+
}
|
|
29574
|
+
translateMessages(messages) {
|
|
29575
|
+
const filtered = messages.filter((m) => m.role !== "system");
|
|
29576
|
+
const translated = filtered.map((msg) => {
|
|
29577
|
+
if (msg.role === "tool") {
|
|
29578
|
+
const content$1 = msg.content.map((part) => {
|
|
29579
|
+
if (part.toolResult) return {
|
|
29580
|
+
type: "tool_result",
|
|
29581
|
+
tool_use_id: part.toolResult.toolCallId,
|
|
29582
|
+
content: part.toolResult.content,
|
|
29583
|
+
is_error: part.toolResult.isError
|
|
29584
|
+
};
|
|
29585
|
+
return {
|
|
29586
|
+
type: "text",
|
|
29587
|
+
text: part.text ?? ""
|
|
29588
|
+
};
|
|
29589
|
+
});
|
|
29590
|
+
return {
|
|
29591
|
+
role: "user",
|
|
29592
|
+
content: content$1
|
|
29593
|
+
};
|
|
29594
|
+
}
|
|
29595
|
+
const role = msg.role;
|
|
29596
|
+
const content = msg.content.map((part) => {
|
|
29597
|
+
if (part.kind === "text") return {
|
|
29598
|
+
type: "text",
|
|
29599
|
+
text: part.text ?? ""
|
|
29600
|
+
};
|
|
29601
|
+
if (part.kind === "tool_call" && part.toolCall) return {
|
|
29602
|
+
type: "tool_use",
|
|
29603
|
+
id: part.toolCall.id,
|
|
29604
|
+
name: part.toolCall.name,
|
|
29605
|
+
input: part.toolCall.arguments
|
|
29606
|
+
};
|
|
29607
|
+
if (part.kind === "tool_result" && part.toolResult) return {
|
|
29608
|
+
type: "tool_result",
|
|
29609
|
+
tool_use_id: part.toolResult.toolCallId,
|
|
29610
|
+
content: part.toolResult.content,
|
|
29611
|
+
is_error: part.toolResult.isError
|
|
29612
|
+
};
|
|
29613
|
+
return {
|
|
29614
|
+
type: "text",
|
|
29615
|
+
text: part.text ?? ""
|
|
29616
|
+
};
|
|
29617
|
+
});
|
|
29618
|
+
return {
|
|
29619
|
+
role,
|
|
29620
|
+
content
|
|
29621
|
+
};
|
|
29622
|
+
});
|
|
29623
|
+
const merged = [];
|
|
29624
|
+
for (const msg of translated) {
|
|
29625
|
+
const last = merged[merged.length - 1];
|
|
29626
|
+
if (last !== void 0 && last.role === msg.role) last.content = [...last.content, ...msg.content];
|
|
29627
|
+
else merged.push({
|
|
29628
|
+
role: msg.role,
|
|
29629
|
+
content: [...msg.content]
|
|
29630
|
+
});
|
|
29631
|
+
}
|
|
29632
|
+
return merged;
|
|
29633
|
+
}
|
|
29634
|
+
translateTools(tools) {
|
|
29635
|
+
return tools.map((tool) => ({
|
|
29636
|
+
name: tool.name,
|
|
29637
|
+
description: tool.description,
|
|
29638
|
+
input_schema: tool.parameters
|
|
29639
|
+
}));
|
|
29640
|
+
}
|
|
29641
|
+
mapToolChoice(toolChoice) {
|
|
29642
|
+
if (!toolChoice || toolChoice === "none") return void 0;
|
|
29643
|
+
if (toolChoice === "auto") return { type: "auto" };
|
|
29644
|
+
if (toolChoice === "required") return { type: "any" };
|
|
29645
|
+
if (typeof toolChoice === "object" && toolChoice.type === "function") return {
|
|
29646
|
+
type: "tool",
|
|
29647
|
+
name: toolChoice.name
|
|
29648
|
+
};
|
|
29649
|
+
return void 0;
|
|
29650
|
+
}
|
|
29651
|
+
parseResponse(raw, model) {
|
|
29652
|
+
let content = "";
|
|
29653
|
+
const toolCalls = [];
|
|
29654
|
+
for (const block of raw.content) if (block.type === "text") content += block.text;
|
|
29655
|
+
else if (block.type === "tool_use") toolCalls.push({
|
|
29656
|
+
id: block.id,
|
|
29657
|
+
name: block.name,
|
|
29658
|
+
arguments: block.input,
|
|
29659
|
+
rawArguments: JSON.stringify(block.input)
|
|
29660
|
+
});
|
|
29661
|
+
const stopReason = mapStopReason(raw.stop_reason);
|
|
29662
|
+
const usage = {
|
|
29663
|
+
inputTokens: raw.usage.input_tokens,
|
|
29664
|
+
outputTokens: raw.usage.output_tokens,
|
|
29665
|
+
totalTokens: raw.usage.input_tokens + raw.usage.output_tokens,
|
|
29666
|
+
...raw.usage.cache_read_input_tokens !== void 0 ? { cacheReadTokens: raw.usage.cache_read_input_tokens } : {},
|
|
29667
|
+
...raw.usage.cache_creation_input_tokens !== void 0 ? { cacheWriteTokens: raw.usage.cache_creation_input_tokens } : {}
|
|
29668
|
+
};
|
|
29669
|
+
return {
|
|
29670
|
+
content,
|
|
29671
|
+
toolCalls,
|
|
29672
|
+
usage,
|
|
29673
|
+
model: raw.model ?? model,
|
|
29674
|
+
stopReason,
|
|
29675
|
+
providerMetadata: { raw },
|
|
29676
|
+
id: raw.id
|
|
29677
|
+
};
|
|
29678
|
+
}
|
|
29679
|
+
};
|
|
29680
|
+
|
|
29681
|
+
//#endregion
|
|
29682
|
+
//#region packages/factory/dist/llm/providers/openai.js
|
|
29683
|
+
var OpenAIAdapter = class {
|
|
29684
|
+
name = "openai";
|
|
29685
|
+
apiKey;
|
|
29686
|
+
baseUrl;
|
|
29687
|
+
orgId;
|
|
29688
|
+
projectId;
|
|
29689
|
+
timeout;
|
|
29690
|
+
constructor(options = {}) {
|
|
29691
|
+
const apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"];
|
|
29692
|
+
if (!apiKey) throw new Error("OPENAI_API_KEY is required");
|
|
29693
|
+
this.apiKey = apiKey;
|
|
29694
|
+
const rawBaseUrl = options.baseUrl ?? process.env["OPENAI_BASE_URL"] ?? "https://api.openai.com/v1";
|
|
29695
|
+
this.baseUrl = rawBaseUrl.replace(/\/$/, "");
|
|
29696
|
+
this.orgId = options.orgId ?? process.env["OPENAI_ORG_ID"];
|
|
29697
|
+
this.projectId = options.projectId ?? process.env["OPENAI_PROJECT_ID"];
|
|
29698
|
+
this.timeout = options.timeout ?? 3e5;
|
|
29699
|
+
}
|
|
29700
|
+
_defaultHeaders() {
|
|
29701
|
+
const headers = {
|
|
29702
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
29703
|
+
"Content-Type": "application/json"
|
|
29704
|
+
};
|
|
29705
|
+
if (this.orgId) headers["OpenAI-Organization"] = this.orgId;
|
|
29706
|
+
if (this.projectId) headers["OpenAI-Project"] = this.projectId;
|
|
29707
|
+
return headers;
|
|
29708
|
+
}
|
|
29709
|
+
_translateContentParts(content, role) {
|
|
29710
|
+
const parts = [];
|
|
29711
|
+
for (const part of content) if (part.kind === "text" && part.text !== void 0) if (role === "user") parts.push({
|
|
29712
|
+
type: "input_text",
|
|
29713
|
+
text: part.text
|
|
29714
|
+
});
|
|
29715
|
+
else parts.push({
|
|
29716
|
+
type: "output_text",
|
|
29717
|
+
text: part.text
|
|
29718
|
+
});
|
|
29719
|
+
else if (part.kind === "tool_call" && part.toolCall) parts.push({
|
|
29720
|
+
type: "function_call",
|
|
29721
|
+
call_id: part.toolCall.id,
|
|
29722
|
+
name: part.toolCall.name,
|
|
29723
|
+
arguments: part.toolCall.rawArguments ?? JSON.stringify(part.toolCall.arguments)
|
|
29724
|
+
});
|
|
29725
|
+
return parts;
|
|
29726
|
+
}
|
|
29727
|
+
_translateMessages(request, body) {
|
|
29728
|
+
if (request.systemPrompt) body.instructions = request.systemPrompt;
|
|
29729
|
+
const input = [];
|
|
29730
|
+
for (const msg of request.messages) {
|
|
29731
|
+
if (msg.role === "system") continue;
|
|
29732
|
+
if (msg.role === "user") input.push({
|
|
29733
|
+
type: "message",
|
|
29734
|
+
role: "user",
|
|
29735
|
+
content: this._translateContentParts(msg.content, "user")
|
|
29736
|
+
});
|
|
29737
|
+
else if (msg.role === "assistant") input.push({
|
|
29738
|
+
type: "message",
|
|
29739
|
+
role: "assistant",
|
|
29740
|
+
content: this._translateContentParts(msg.content, "assistant")
|
|
29741
|
+
});
|
|
29742
|
+
else if (msg.role === "tool") {
|
|
29743
|
+
const callId = msg.toolCallId ?? this._extractToolCallId(msg);
|
|
29744
|
+
const output = this._extractToolOutput(msg);
|
|
29745
|
+
input.push({
|
|
29746
|
+
type: "function_call_output",
|
|
29747
|
+
call_id: callId,
|
|
29748
|
+
output
|
|
29749
|
+
});
|
|
29750
|
+
}
|
|
29751
|
+
}
|
|
29752
|
+
body.input = input;
|
|
29753
|
+
}
|
|
29754
|
+
_extractToolCallId(msg) {
|
|
29755
|
+
for (const part of msg.content) if (part.kind === "tool_result" && part.toolResult?.toolCallId) return part.toolResult.toolCallId;
|
|
29756
|
+
return "";
|
|
29757
|
+
}
|
|
29758
|
+
_extractToolOutput(msg) {
|
|
29759
|
+
for (const part of msg.content) if (part.kind === "text" && part.text !== void 0) return part.text;
|
|
29760
|
+
for (const part of msg.content) if (part.kind === "tool_result" && part.toolResult?.content !== void 0) return String(part.toolResult.content);
|
|
29761
|
+
return "";
|
|
29762
|
+
}
|
|
29763
|
+
_translateTools(request, body) {
|
|
29764
|
+
if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
|
|
29765
|
+
type: "function",
|
|
29766
|
+
function: {
|
|
29767
|
+
name: t.name,
|
|
29768
|
+
description: t.description,
|
|
29769
|
+
parameters: t.parameters
|
|
29770
|
+
}
|
|
29771
|
+
}));
|
|
29772
|
+
if (request.toolChoice !== void 0) {
|
|
29773
|
+
if (typeof request.toolChoice === "string") body.tool_choice = request.toolChoice;
|
|
29774
|
+
else if (request.toolChoice.type === "function") body.tool_choice = {
|
|
29775
|
+
type: "function",
|
|
29776
|
+
function: { name: request.toolChoice.name }
|
|
29777
|
+
};
|
|
29778
|
+
}
|
|
29779
|
+
}
|
|
29780
|
+
_parseUsage(usage) {
|
|
29781
|
+
const inputTokens = usage.input_tokens;
|
|
29782
|
+
const outputTokens = usage.output_tokens;
|
|
29783
|
+
const totalTokens = inputTokens + outputTokens;
|
|
29784
|
+
const result = {
|
|
29785
|
+
inputTokens,
|
|
29786
|
+
outputTokens,
|
|
29787
|
+
totalTokens
|
|
29788
|
+
};
|
|
29789
|
+
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens;
|
|
29790
|
+
if (reasoningTokens !== void 0 && reasoningTokens > 0) result.reasoningTokens = reasoningTokens;
|
|
29791
|
+
const cacheReadTokens = usage.input_tokens_details?.cached_tokens;
|
|
29792
|
+
if (cacheReadTokens !== void 0 && cacheReadTokens > 0) result.cacheReadTokens = cacheReadTokens;
|
|
29793
|
+
return result;
|
|
29794
|
+
}
|
|
29795
|
+
_mapStopReason(response, hasToolCalls) {
|
|
29796
|
+
if (response.status === "completed") return hasToolCalls ? "tool_calls" : "stop";
|
|
29797
|
+
if (response.status === "incomplete") {
|
|
29798
|
+
const reason = response.incomplete_details?.reason;
|
|
29799
|
+
if (reason === "max_output_tokens") return "length";
|
|
29800
|
+
if (reason === "content_filter") return "content_filter";
|
|
29801
|
+
}
|
|
29802
|
+
return "other";
|
|
29803
|
+
}
|
|
29804
|
+
async complete(request) {
|
|
29805
|
+
const body = {
|
|
29806
|
+
model: request.model,
|
|
29807
|
+
input: []
|
|
29808
|
+
};
|
|
29809
|
+
this._translateMessages(request, body);
|
|
29810
|
+
this._translateTools(request, body);
|
|
29811
|
+
if (request.maxTokens !== void 0) body.max_output_tokens = request.maxTokens;
|
|
29812
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
29813
|
+
if (request.reasoningEffort !== void 0) body.reasoning = { effort: request.reasoningEffort };
|
|
29814
|
+
if (request.extra?.openai) Object.assign(body, request.extra.openai);
|
|
29815
|
+
const controller = new AbortController();
|
|
29816
|
+
const timer = setTimeout(() => {
|
|
29817
|
+
controller.abort();
|
|
29818
|
+
}, this.timeout);
|
|
29819
|
+
let rawResponse;
|
|
29820
|
+
try {
|
|
29821
|
+
rawResponse = await fetch(`${this.baseUrl}/responses`, {
|
|
29822
|
+
method: "POST",
|
|
29823
|
+
headers: this._defaultHeaders(),
|
|
29824
|
+
body: JSON.stringify(body),
|
|
29825
|
+
signal: controller.signal
|
|
29826
|
+
});
|
|
29827
|
+
} finally {
|
|
29828
|
+
clearTimeout(timer);
|
|
29829
|
+
}
|
|
29830
|
+
if (!rawResponse.ok) {
|
|
29831
|
+
let errorMessage = `OpenAI API error: ${rawResponse.status}`;
|
|
29832
|
+
try {
|
|
29833
|
+
const errorBody = await rawResponse.json();
|
|
29834
|
+
if (errorBody.error?.message) errorMessage = `OpenAI API error ${rawResponse.status}: ${errorBody.error.message}`;
|
|
29835
|
+
} catch {}
|
|
29836
|
+
throw new LLMError(errorMessage, rawResponse.status, "openai");
|
|
29837
|
+
}
|
|
29838
|
+
const data = await rawResponse.json();
|
|
29839
|
+
let content = "";
|
|
29840
|
+
const toolCalls = [];
|
|
29841
|
+
for (const item of data.output) if (item.type === "message") {
|
|
29842
|
+
for (const part of item.content) if (part.type === "output_text") content += part.text;
|
|
29843
|
+
} else if (item.type === "function_call") {
|
|
29844
|
+
let parsedArgs = {};
|
|
29845
|
+
try {
|
|
29846
|
+
parsedArgs = JSON.parse(item.arguments);
|
|
29847
|
+
} catch {}
|
|
29848
|
+
toolCalls.push({
|
|
29849
|
+
id: item.call_id,
|
|
29850
|
+
name: item.name,
|
|
29851
|
+
arguments: parsedArgs,
|
|
29852
|
+
rawArguments: item.arguments
|
|
29853
|
+
});
|
|
29854
|
+
}
|
|
29855
|
+
const stopReason = this._mapStopReason(data, toolCalls.length > 0);
|
|
29856
|
+
const usage = this._parseUsage(data.usage);
|
|
29857
|
+
return {
|
|
29858
|
+
content,
|
|
29859
|
+
toolCalls,
|
|
29860
|
+
usage,
|
|
29861
|
+
model: data.model,
|
|
29862
|
+
stopReason,
|
|
29863
|
+
providerMetadata: { id: data.id },
|
|
29864
|
+
id: data.id
|
|
29865
|
+
};
|
|
29866
|
+
}
|
|
29867
|
+
async *stream(request) {
|
|
29868
|
+
const body = {
|
|
29869
|
+
model: request.model,
|
|
29870
|
+
input: [],
|
|
29871
|
+
stream: true
|
|
29872
|
+
};
|
|
29873
|
+
this._translateMessages(request, body);
|
|
29874
|
+
this._translateTools(request, body);
|
|
29875
|
+
if (request.maxTokens !== void 0) body.max_output_tokens = request.maxTokens;
|
|
29876
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
29877
|
+
if (request.reasoningEffort !== void 0) body.reasoning = { effort: request.reasoningEffort };
|
|
29878
|
+
if (request.extra) Object.assign(body, request.extra);
|
|
29879
|
+
const controller = new AbortController();
|
|
29880
|
+
const timer = setTimeout(() => {
|
|
29881
|
+
controller.abort();
|
|
29882
|
+
}, this.timeout);
|
|
29883
|
+
let rawResponse;
|
|
29884
|
+
try {
|
|
29885
|
+
rawResponse = await fetch(`${this.baseUrl}/responses`, {
|
|
29886
|
+
method: "POST",
|
|
29887
|
+
headers: this._defaultHeaders(),
|
|
29888
|
+
body: JSON.stringify(body),
|
|
29889
|
+
signal: controller.signal
|
|
29890
|
+
});
|
|
29891
|
+
} catch (err) {
|
|
29892
|
+
clearTimeout(timer);
|
|
29893
|
+
throw err;
|
|
29894
|
+
}
|
|
29895
|
+
if (!rawResponse.ok) {
|
|
29896
|
+
clearTimeout(timer);
|
|
29897
|
+
let errorMessage = `OpenAI API error: ${rawResponse.status}`;
|
|
29898
|
+
try {
|
|
29899
|
+
const errorBody = await rawResponse.json();
|
|
29900
|
+
if (errorBody.error?.message) errorMessage = `OpenAI API error ${rawResponse.status}: ${errorBody.error.message}`;
|
|
29901
|
+
} catch {}
|
|
29902
|
+
throw new LLMError(errorMessage, rawResponse.status, "openai");
|
|
29903
|
+
}
|
|
29904
|
+
try {
|
|
29905
|
+
const text = await rawResponse.text();
|
|
29906
|
+
const lines = text.split("\n");
|
|
29907
|
+
let started = false;
|
|
29908
|
+
let currentEventName = "";
|
|
29909
|
+
for (const line of lines) {
|
|
29910
|
+
const trimmed = line.trim();
|
|
29911
|
+
if (trimmed.startsWith("event: ")) currentEventName = trimmed.slice(7).trim();
|
|
29912
|
+
else if (trimmed.startsWith("data: ")) {
|
|
29913
|
+
const dataStr = trimmed.slice(6).trim();
|
|
29914
|
+
if (dataStr === "[DONE]") break;
|
|
29915
|
+
let eventData;
|
|
29916
|
+
try {
|
|
29917
|
+
eventData = JSON.parse(dataStr);
|
|
29918
|
+
} catch {
|
|
29919
|
+
continue;
|
|
29920
|
+
}
|
|
29921
|
+
if (!started) {
|
|
29922
|
+
started = true;
|
|
29923
|
+
yield { type: "message_start" };
|
|
29924
|
+
}
|
|
29925
|
+
if (currentEventName === "response.output_text.delta") yield {
|
|
29926
|
+
type: "text_delta",
|
|
29927
|
+
delta: eventData["delta"] ?? ""
|
|
29928
|
+
};
|
|
29929
|
+
else if (currentEventName === "response.function_call_arguments.delta") {
|
|
29930
|
+
const itemId = eventData["item_id"];
|
|
29931
|
+
const toolCallDelta = { rawArguments: eventData["delta"] ?? "" };
|
|
29932
|
+
if (itemId !== void 0) toolCallDelta.id = itemId;
|
|
29933
|
+
yield {
|
|
29934
|
+
type: "tool_call_delta",
|
|
29935
|
+
toolCall: toolCallDelta
|
|
29936
|
+
};
|
|
29937
|
+
} else if (currentEventName === "response.output_item.done") {
|
|
29938
|
+
const itemType = eventData["type"];
|
|
29939
|
+
if (itemType === "function_call") {
|
|
29940
|
+
const rawArgs = eventData["arguments"] ?? "";
|
|
29941
|
+
let parsedArgs = {};
|
|
29942
|
+
try {
|
|
29943
|
+
parsedArgs = JSON.parse(rawArgs);
|
|
29944
|
+
} catch {}
|
|
29945
|
+
const callId = eventData["call_id"];
|
|
29946
|
+
const callName = eventData["name"];
|
|
29947
|
+
const doneToolCall = { arguments: parsedArgs };
|
|
29948
|
+
if (callId !== void 0) doneToolCall.id = callId;
|
|
29949
|
+
if (callName !== void 0) doneToolCall.name = callName;
|
|
29950
|
+
yield {
|
|
29951
|
+
type: "tool_call_delta",
|
|
29952
|
+
toolCall: doneToolCall
|
|
29953
|
+
};
|
|
29954
|
+
}
|
|
29955
|
+
} else if (currentEventName === "response.completed" || currentEventName === "response.created") {
|
|
29956
|
+
if (currentEventName === "response.completed") {
|
|
29957
|
+
const completedResponse = eventData["response"] ?? eventData;
|
|
29958
|
+
const hasToolCalls = Array.isArray(completedResponse.output) && completedResponse.output.some((o) => o.type === "function_call");
|
|
29959
|
+
const stopReason = this._mapStopReason(completedResponse, hasToolCalls);
|
|
29960
|
+
yield {
|
|
29961
|
+
type: "message_stop",
|
|
29962
|
+
usage: this._parseUsage(completedResponse.usage),
|
|
29963
|
+
finishReason: { reason: stopReason }
|
|
29964
|
+
};
|
|
29965
|
+
}
|
|
29966
|
+
}
|
|
29967
|
+
}
|
|
29968
|
+
}
|
|
29969
|
+
} finally {
|
|
29970
|
+
clearTimeout(timer);
|
|
29971
|
+
}
|
|
29972
|
+
}
|
|
29973
|
+
};
|
|
29974
|
+
|
|
29975
|
+
//#endregion
|
|
29976
|
+
//#region packages/factory/dist/llm/providers/gemini.js
|
|
29977
|
+
var GeminiAdapter = class {
|
|
29978
|
+
name = "gemini";
|
|
29979
|
+
apiKey;
|
|
29980
|
+
baseUrl;
|
|
29981
|
+
timeout;
|
|
29982
|
+
constructor(options = {}) {
|
|
29983
|
+
const apiKey = options.apiKey ?? process.env["GEMINI_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
|
|
29984
|
+
if (!apiKey) throw new Error("GEMINI_API_KEY is required");
|
|
29985
|
+
this.apiKey = apiKey;
|
|
29986
|
+
const rawBaseUrl = options.baseUrl ?? process.env["GEMINI_BASE_URL"] ?? "https://generativelanguage.googleapis.com";
|
|
29987
|
+
this.baseUrl = rawBaseUrl.replace(/\/$/, "");
|
|
29988
|
+
this.timeout = options.timeout ?? 3e5;
|
|
29989
|
+
}
|
|
29990
|
+
_buildUrl(model, streaming) {
|
|
29991
|
+
const action = streaming ? "streamGenerateContent" : "generateContent";
|
|
29992
|
+
let url = `${this.baseUrl}/v1beta/models/${model}:${action}?key=${this.apiKey}`;
|
|
29993
|
+
if (streaming) url += "&alt=sse";
|
|
29994
|
+
return url;
|
|
29995
|
+
}
|
|
29996
|
+
_wrapResponse(content) {
|
|
29997
|
+
try {
|
|
29998
|
+
const parsed = JSON.parse(content);
|
|
29999
|
+
if (typeof parsed === "object" && parsed !== null) return parsed;
|
|
30000
|
+
} catch {}
|
|
30001
|
+
return { result: content };
|
|
30002
|
+
}
|
|
30003
|
+
_translateParts(content) {
|
|
30004
|
+
const parts = [];
|
|
30005
|
+
for (const part of content) if (part.kind === "text" && part.text !== void 0) parts.push({ text: part.text });
|
|
30006
|
+
else if (part.kind === "tool_call" && part.toolCall) parts.push({ functionCall: {
|
|
30007
|
+
name: part.toolCall.name,
|
|
30008
|
+
args: part.toolCall.arguments
|
|
30009
|
+
} });
|
|
30010
|
+
return parts;
|
|
30011
|
+
}
|
|
30012
|
+
/**
|
|
30013
|
+
* Scan all messages to build a mapping from synthetic tool call IDs to function names.
|
|
30014
|
+
* This is needed to translate tool-result messages back into Gemini's functionResponse format.
|
|
30015
|
+
*/
|
|
30016
|
+
_buildToolCallIdMap(messages) {
|
|
30017
|
+
const idToName = new Map();
|
|
30018
|
+
for (const msg of messages) if (msg.role === "assistant") {
|
|
30019
|
+
for (const part of msg.content) if (part.kind === "tool_call" && part.toolCall) idToName.set(part.toolCall.id, part.toolCall.name);
|
|
30020
|
+
}
|
|
30021
|
+
return idToName;
|
|
30022
|
+
}
|
|
30023
|
+
_translateMessages(request, body) {
|
|
30024
|
+
if (request.systemPrompt) body.systemInstruction = { parts: [{ text: request.systemPrompt }] };
|
|
30025
|
+
const idToName = this._buildToolCallIdMap(request.messages);
|
|
30026
|
+
const contents = [];
|
|
30027
|
+
for (const msg of request.messages) {
|
|
30028
|
+
if (msg.role === "system") continue;
|
|
30029
|
+
if (msg.role === "user") contents.push({
|
|
30030
|
+
role: "user",
|
|
30031
|
+
parts: this._translateParts(msg.content)
|
|
30032
|
+
});
|
|
30033
|
+
else if (msg.role === "assistant") contents.push({
|
|
30034
|
+
role: "model",
|
|
30035
|
+
parts: this._translateParts(msg.content)
|
|
30036
|
+
});
|
|
30037
|
+
else if (msg.role === "tool") {
|
|
30038
|
+
const toolResultParts = [];
|
|
30039
|
+
for (const part of msg.content) if (part.kind === "tool_result" && part.toolResult) {
|
|
30040
|
+
const { toolCallId, content } = part.toolResult;
|
|
30041
|
+
const functionName = idToName.get(toolCallId) ?? toolCallId;
|
|
30042
|
+
toolResultParts.push({ functionResponse: {
|
|
30043
|
+
name: functionName,
|
|
30044
|
+
response: this._wrapResponse(String(content))
|
|
30045
|
+
} });
|
|
30046
|
+
}
|
|
30047
|
+
if (toolResultParts.length === 0) {
|
|
30048
|
+
const callId = msg.toolCallId ?? "";
|
|
30049
|
+
const functionName = idToName.get(callId) ?? callId;
|
|
30050
|
+
const textContent = msg.content.filter((p) => p.kind === "text").map((p) => p.text ?? "").join("");
|
|
30051
|
+
toolResultParts.push({ functionResponse: {
|
|
30052
|
+
name: functionName,
|
|
30053
|
+
response: this._wrapResponse(textContent)
|
|
30054
|
+
} });
|
|
30055
|
+
}
|
|
30056
|
+
contents.push({
|
|
30057
|
+
role: "user",
|
|
30058
|
+
parts: toolResultParts
|
|
30059
|
+
});
|
|
30060
|
+
}
|
|
30061
|
+
}
|
|
30062
|
+
body.contents = contents;
|
|
30063
|
+
}
|
|
30064
|
+
_translateTools(request, body) {
|
|
30065
|
+
if (request.tools && request.tools.length > 0) body.tools = [{ functionDeclarations: request.tools.map((t) => ({
|
|
30066
|
+
name: t.name,
|
|
30067
|
+
description: t.description,
|
|
30068
|
+
parameters: t.parameters
|
|
30069
|
+
})) }];
|
|
30070
|
+
if (request.toolChoice !== void 0) {
|
|
30071
|
+
if (request.toolChoice === "auto") body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
|
|
30072
|
+
else if (request.toolChoice === "none") body.toolConfig = { functionCallingConfig: { mode: "NONE" } };
|
|
30073
|
+
else if (request.toolChoice === "required") body.toolConfig = { functionCallingConfig: { mode: "ANY" } };
|
|
30074
|
+
else if (typeof request.toolChoice === "object" && request.toolChoice.type === "function") body.toolConfig = { functionCallingConfig: {
|
|
30075
|
+
mode: "ANY",
|
|
30076
|
+
allowedFunctionNames: [request.toolChoice.name]
|
|
30077
|
+
} };
|
|
30078
|
+
}
|
|
30079
|
+
}
|
|
30080
|
+
_parseUsage(usageMetadata) {
|
|
30081
|
+
const inputTokens = usageMetadata.promptTokenCount ?? 0;
|
|
30082
|
+
const outputTokens = usageMetadata.candidatesTokenCount ?? 0;
|
|
30083
|
+
const totalTokens = inputTokens + outputTokens;
|
|
30084
|
+
const result = {
|
|
30085
|
+
inputTokens,
|
|
30086
|
+
outputTokens,
|
|
30087
|
+
totalTokens
|
|
30088
|
+
};
|
|
30089
|
+
if (usageMetadata.thoughtsTokenCount !== void 0 && usageMetadata.thoughtsTokenCount > 0) result.reasoningTokens = usageMetadata.thoughtsTokenCount;
|
|
30090
|
+
if (usageMetadata.cachedContentTokenCount !== void 0 && usageMetadata.cachedContentTokenCount > 0) result.cacheReadTokens = usageMetadata.cachedContentTokenCount;
|
|
30091
|
+
return result;
|
|
30092
|
+
}
|
|
30093
|
+
_mapStopReason(finishReason, hasToolCalls) {
|
|
30094
|
+
if (hasToolCalls) return "tool_calls";
|
|
30095
|
+
switch (finishReason) {
|
|
30096
|
+
case "STOP": return "stop";
|
|
30097
|
+
case "MAX_TOKENS": return "length";
|
|
30098
|
+
case "SAFETY":
|
|
30099
|
+
case "RECITATION": return "content_filter";
|
|
30100
|
+
default: return "other";
|
|
30101
|
+
}
|
|
30102
|
+
}
|
|
30103
|
+
async complete(request) {
|
|
30104
|
+
const body = { contents: [] };
|
|
30105
|
+
this._translateMessages(request, body);
|
|
30106
|
+
this._translateTools(request, body);
|
|
30107
|
+
const generationConfig = {};
|
|
30108
|
+
if (request.maxTokens !== void 0) generationConfig.maxOutputTokens = request.maxTokens;
|
|
30109
|
+
if (request.temperature !== void 0) generationConfig.temperature = request.temperature;
|
|
30110
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
30111
|
+
if (request.extra?.gemini) Object.assign(body, request.extra.gemini);
|
|
30112
|
+
const controller = new AbortController();
|
|
30113
|
+
const timer = setTimeout(() => {
|
|
30114
|
+
controller.abort();
|
|
30115
|
+
}, this.timeout);
|
|
30116
|
+
let rawResponse;
|
|
30117
|
+
try {
|
|
30118
|
+
rawResponse = await fetch(this._buildUrl(request.model, false), {
|
|
30119
|
+
method: "POST",
|
|
30120
|
+
headers: { "Content-Type": "application/json" },
|
|
30121
|
+
body: JSON.stringify(body),
|
|
30122
|
+
signal: controller.signal
|
|
30123
|
+
});
|
|
30124
|
+
} finally {
|
|
30125
|
+
clearTimeout(timer);
|
|
30126
|
+
}
|
|
30127
|
+
if (!rawResponse.ok) {
|
|
30128
|
+
let errorMessage = `[gemini] ${rawResponse.status}`;
|
|
30129
|
+
try {
|
|
30130
|
+
const errorBody = await rawResponse.json();
|
|
30131
|
+
if (errorBody.error?.message) errorMessage = `[gemini] ${rawResponse.status}: ${errorBody.error.message}`;
|
|
30132
|
+
} catch {}
|
|
30133
|
+
throw new LLMError(errorMessage, rawResponse.status, "gemini");
|
|
30134
|
+
}
|
|
30135
|
+
const data = await rawResponse.json();
|
|
30136
|
+
const candidate = data.candidates?.[0];
|
|
30137
|
+
const parts = candidate?.content?.parts ?? [];
|
|
30138
|
+
const syntheticIdMap = new Map();
|
|
30139
|
+
const toolCalls = [];
|
|
30140
|
+
let content = "";
|
|
30141
|
+
for (const part of parts) if ("text" in part && part.text !== void 0) {
|
|
30142
|
+
const thoughtPart = part;
|
|
30143
|
+
if (!thoughtPart.thought) content += thoughtPart.text;
|
|
30144
|
+
} else if ("functionCall" in part) {
|
|
30145
|
+
const synthId = `call_${randomUUID()}`;
|
|
30146
|
+
syntheticIdMap.set(synthId, part.functionCall.name);
|
|
30147
|
+
toolCalls.push({
|
|
30148
|
+
id: synthId,
|
|
30149
|
+
name: part.functionCall.name,
|
|
30150
|
+
arguments: part.functionCall.args ?? {},
|
|
30151
|
+
rawArguments: JSON.stringify(part.functionCall.args)
|
|
30152
|
+
});
|
|
30153
|
+
}
|
|
30154
|
+
const stopReason = this._mapStopReason(candidate?.finishReason, toolCalls.length > 0);
|
|
30155
|
+
const usage = this._parseUsage(data.usageMetadata ?? {
|
|
30156
|
+
promptTokenCount: 0,
|
|
30157
|
+
candidatesTokenCount: 0
|
|
30158
|
+
});
|
|
30159
|
+
return {
|
|
30160
|
+
content,
|
|
30161
|
+
toolCalls,
|
|
30162
|
+
usage,
|
|
30163
|
+
model: request.model,
|
|
30164
|
+
stopReason,
|
|
30165
|
+
providerMetadata: { model: data.modelVersion ?? request.model }
|
|
30166
|
+
};
|
|
30167
|
+
}
|
|
30168
|
+
async *stream(request) {
|
|
30169
|
+
const body = { contents: [] };
|
|
30170
|
+
this._translateMessages(request, body);
|
|
30171
|
+
this._translateTools(request, body);
|
|
30172
|
+
const generationConfig = {};
|
|
30173
|
+
if (request.maxTokens !== void 0) generationConfig.maxOutputTokens = request.maxTokens;
|
|
30174
|
+
if (request.temperature !== void 0) generationConfig.temperature = request.temperature;
|
|
30175
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
30176
|
+
if (request.extra) Object.assign(body, request.extra);
|
|
30177
|
+
const controller = new AbortController();
|
|
30178
|
+
const timer = setTimeout(() => {
|
|
30179
|
+
controller.abort();
|
|
30180
|
+
}, this.timeout);
|
|
30181
|
+
let rawResponse;
|
|
30182
|
+
try {
|
|
30183
|
+
rawResponse = await fetch(this._buildUrl(request.model, true), {
|
|
30184
|
+
method: "POST",
|
|
30185
|
+
headers: { "Content-Type": "application/json" },
|
|
30186
|
+
body: JSON.stringify(body),
|
|
30187
|
+
signal: controller.signal
|
|
30188
|
+
});
|
|
30189
|
+
} catch (err) {
|
|
30190
|
+
clearTimeout(timer);
|
|
30191
|
+
throw err;
|
|
30192
|
+
}
|
|
30193
|
+
if (!rawResponse.ok) {
|
|
30194
|
+
clearTimeout(timer);
|
|
30195
|
+
let errorMessage = `[gemini] ${rawResponse.status}`;
|
|
30196
|
+
try {
|
|
30197
|
+
const errorBody = await rawResponse.json();
|
|
30198
|
+
if (errorBody.error?.message) errorMessage = `[gemini] ${rawResponse.status}: ${errorBody.error.message}`;
|
|
30199
|
+
} catch {}
|
|
30200
|
+
throw new LLMError(errorMessage, rawResponse.status, "gemini");
|
|
30201
|
+
}
|
|
30202
|
+
try {
|
|
30203
|
+
const text = await rawResponse.text();
|
|
30204
|
+
const lines = text.split("\n");
|
|
30205
|
+
let started = false;
|
|
30206
|
+
for (const line of lines) {
|
|
30207
|
+
const trimmed = line.trim();
|
|
30208
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
30209
|
+
const dataStr = trimmed.slice(6).trim();
|
|
30210
|
+
if (dataStr === "[DONE]") break;
|
|
30211
|
+
let chunk;
|
|
30212
|
+
try {
|
|
30213
|
+
chunk = JSON.parse(dataStr);
|
|
30214
|
+
} catch {
|
|
30215
|
+
continue;
|
|
30216
|
+
}
|
|
30217
|
+
if (!started) {
|
|
30218
|
+
started = true;
|
|
30219
|
+
yield { type: "message_start" };
|
|
30220
|
+
}
|
|
30221
|
+
const candidateParts = chunk.candidates?.[0]?.content?.parts ?? [];
|
|
30222
|
+
for (const part of candidateParts) if ("text" in part && part.text !== void 0) {
|
|
30223
|
+
const thoughtPart = part;
|
|
30224
|
+
if (!thoughtPart.thought) yield {
|
|
30225
|
+
type: "text_delta",
|
|
30226
|
+
delta: thoughtPart.text
|
|
30227
|
+
};
|
|
30228
|
+
} else if ("functionCall" in part) {
|
|
30229
|
+
const synthId = `call_${randomUUID()}`;
|
|
30230
|
+
yield {
|
|
30231
|
+
type: "tool_call_delta",
|
|
30232
|
+
toolCall: {
|
|
30233
|
+
id: synthId,
|
|
30234
|
+
name: part.functionCall.name,
|
|
30235
|
+
arguments: part.functionCall.args ?? {}
|
|
30236
|
+
}
|
|
30237
|
+
};
|
|
30238
|
+
}
|
|
30239
|
+
if (chunk.usageMetadata) {
|
|
30240
|
+
const finishReason = chunk.candidates?.[0]?.finishReason;
|
|
30241
|
+
const hasToolCalls = candidateParts.some((p) => "functionCall" in p);
|
|
30242
|
+
const stopReason = this._mapStopReason(finishReason, hasToolCalls);
|
|
30243
|
+
yield {
|
|
30244
|
+
type: "message_stop",
|
|
30245
|
+
usage: this._parseUsage(chunk.usageMetadata),
|
|
30246
|
+
finishReason: { reason: stopReason }
|
|
30247
|
+
};
|
|
30248
|
+
}
|
|
30249
|
+
}
|
|
30250
|
+
} finally {
|
|
30251
|
+
clearTimeout(timer);
|
|
30252
|
+
}
|
|
30253
|
+
}
|
|
30254
|
+
};
|
|
30255
|
+
|
|
30256
|
+
//#endregion
|
|
30257
|
+
//#region packages/factory/dist/agent/tools/shared.js
|
|
30258
|
+
/**
|
|
30259
|
+
* Creates the five shared tools: read_file, write_file, shell, grep, glob.
|
|
30260
|
+
*/
|
|
30261
|
+
function createSharedTools(shellTimeoutMs = 1e4) {
|
|
30262
|
+
return [
|
|
30263
|
+
createReadFileTool(),
|
|
30264
|
+
createWriteFileTool(),
|
|
30265
|
+
createShellTool(shellTimeoutMs),
|
|
30266
|
+
createGrepTool(shellTimeoutMs),
|
|
30267
|
+
createGlobTool()
|
|
30268
|
+
];
|
|
30269
|
+
}
|
|
30270
|
+
function createReadFileTool() {
|
|
30271
|
+
return {
|
|
30272
|
+
name: "read_file",
|
|
30273
|
+
description: "Read a file from the filesystem, with optional offset and limit for line ranges. Returns content with 1-based line numbers.",
|
|
30274
|
+
inputSchema: {
|
|
30275
|
+
type: "object",
|
|
30276
|
+
properties: {
|
|
30277
|
+
path: {
|
|
30278
|
+
type: "string",
|
|
30279
|
+
description: "Path to the file to read"
|
|
30280
|
+
},
|
|
30281
|
+
offset: {
|
|
30282
|
+
type: "number",
|
|
30283
|
+
minimum: 1,
|
|
30284
|
+
description: "Line number to start reading from (1-based, optional)"
|
|
30285
|
+
},
|
|
30286
|
+
limit: {
|
|
30287
|
+
type: "number",
|
|
30288
|
+
minimum: 1,
|
|
30289
|
+
description: "Maximum number of lines to read (optional)"
|
|
30290
|
+
}
|
|
30291
|
+
},
|
|
30292
|
+
required: ["path"]
|
|
30293
|
+
},
|
|
30294
|
+
outputTruncation: 5e4,
|
|
30295
|
+
async executor(args, _env) {
|
|
30296
|
+
const raw = await readFile$1(args.path, "utf-8");
|
|
30297
|
+
const lines = raw.split("\n");
|
|
30298
|
+
const offset = args.offset !== void 0 ? args.offset - 1 : 0;
|
|
30299
|
+
const slice = args.limit !== void 0 ? lines.slice(offset, offset + args.limit) : lines.slice(offset);
|
|
30300
|
+
const numbered = slice.map((line, i) => {
|
|
30301
|
+
const lineNum = offset + i + 1;
|
|
30302
|
+
return `${String(lineNum).padStart(3)}\t${line}`;
|
|
30303
|
+
});
|
|
30304
|
+
return numbered.join("\n");
|
|
30305
|
+
}
|
|
30306
|
+
};
|
|
30307
|
+
}
|
|
30308
|
+
function createWriteFileTool() {
|
|
30309
|
+
return {
|
|
30310
|
+
name: "write_file",
|
|
30311
|
+
description: "Write content to a file, creating parent directories if needed.",
|
|
30312
|
+
inputSchema: {
|
|
30313
|
+
type: "object",
|
|
30314
|
+
properties: {
|
|
30315
|
+
path: {
|
|
30316
|
+
type: "string",
|
|
30317
|
+
description: "Path to the file to write"
|
|
30318
|
+
},
|
|
30319
|
+
content: {
|
|
30320
|
+
type: "string",
|
|
30321
|
+
description: "Content to write to the file"
|
|
30322
|
+
}
|
|
30323
|
+
},
|
|
30324
|
+
required: ["path", "content"]
|
|
30325
|
+
},
|
|
30326
|
+
async executor(args, _env) {
|
|
30327
|
+
const dir = dirname$1(args.path);
|
|
30328
|
+
await mkdir$1(dir, { recursive: true });
|
|
30329
|
+
const bytes = Buffer.byteLength(args.content, "utf-8");
|
|
30330
|
+
await writeFile$1(args.path, args.content, "utf-8");
|
|
30331
|
+
return `Wrote ${bytes} bytes to ${args.path}`;
|
|
30332
|
+
}
|
|
30333
|
+
};
|
|
30334
|
+
}
|
|
30335
|
+
function createShellTool(shellTimeoutMs) {
|
|
30336
|
+
return {
|
|
30337
|
+
name: "shell",
|
|
30338
|
+
description: "Execute a shell command and return its output.",
|
|
30339
|
+
inputSchema: {
|
|
30340
|
+
type: "object",
|
|
30341
|
+
properties: {
|
|
30342
|
+
command: {
|
|
30343
|
+
type: "string",
|
|
30344
|
+
description: "Shell command to execute"
|
|
30345
|
+
},
|
|
30346
|
+
timeout_ms: {
|
|
30347
|
+
type: "number",
|
|
30348
|
+
description: "Timeout in milliseconds (optional)"
|
|
30349
|
+
}
|
|
30350
|
+
},
|
|
30351
|
+
required: ["command"]
|
|
30352
|
+
},
|
|
30353
|
+
outputTruncation: 1e4,
|
|
30354
|
+
async executor(args, env) {
|
|
30355
|
+
const timeout = args.timeout_ms !== void 0 ? args.timeout_ms : shellTimeoutMs;
|
|
30356
|
+
const result = await env.exec(args.command, timeout);
|
|
30357
|
+
if (result.exitCode !== 0) throw new Error([result.stdout, result.stderr].filter(Boolean).join("\n") || `Command failed with exit code ${result.exitCode}`);
|
|
30358
|
+
const combined = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
30359
|
+
return combined || `(exit code ${result.exitCode})`;
|
|
30360
|
+
}
|
|
30361
|
+
};
|
|
30362
|
+
}
|
|
30363
|
+
function createGrepTool(shellTimeoutMs) {
|
|
30364
|
+
return {
|
|
30365
|
+
name: "grep",
|
|
30366
|
+
description: "Search for a regex pattern in files. Returns matching lines with filenames and line numbers.",
|
|
30367
|
+
inputSchema: {
|
|
30368
|
+
type: "object",
|
|
30369
|
+
properties: {
|
|
30370
|
+
pattern: {
|
|
30371
|
+
type: "string",
|
|
30372
|
+
description: "Regex pattern to search for"
|
|
30373
|
+
},
|
|
30374
|
+
paths: {
|
|
30375
|
+
type: "array",
|
|
30376
|
+
items: { type: "string" },
|
|
30377
|
+
description: "File paths or directories to search in"
|
|
30378
|
+
}
|
|
30379
|
+
},
|
|
30380
|
+
required: ["pattern", "paths"]
|
|
30381
|
+
},
|
|
30382
|
+
outputTruncation: 1e4,
|
|
30383
|
+
async executor(args, env) {
|
|
30384
|
+
if (/[`$\\]/.test(args.pattern) && /[`$]/.test(args.pattern)) return nodeGrepFallback(args.pattern, args.paths);
|
|
30385
|
+
const escapedPattern = args.pattern.replace(/"/g, "\\\"");
|
|
30386
|
+
const escapedPaths = args.paths.map((p) => `"${p.replace(/"/g, "\\\"")}"`).join(" ");
|
|
30387
|
+
const cmd = `rg --no-heading -n -- "${escapedPattern}" ${escapedPaths} 2>&1`;
|
|
30388
|
+
try {
|
|
30389
|
+
const result = await env.exec(cmd, shellTimeoutMs);
|
|
30390
|
+
if (result.exitCode === 0 || result.stdout.trim() !== "") return result.stdout || result.stderr;
|
|
30391
|
+
if (result.exitCode === 1) return result.stdout;
|
|
30392
|
+
throw new Error("rg not available");
|
|
30393
|
+
} catch (_err) {
|
|
30394
|
+
return nodeGrepFallback(args.pattern, args.paths);
|
|
30395
|
+
}
|
|
30396
|
+
}
|
|
30397
|
+
};
|
|
30398
|
+
}
|
|
30399
|
+
async function nodeGrepFallback(pattern, paths) {
|
|
30400
|
+
const regex$1 = new RegExp(pattern);
|
|
30401
|
+
const results = [];
|
|
30402
|
+
async function scanFile(filePath) {
|
|
30403
|
+
try {
|
|
30404
|
+
const content = await readFile$1(filePath, "utf-8");
|
|
30405
|
+
const lines = content.split("\n");
|
|
30406
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30407
|
+
const line = lines[i] ?? "";
|
|
30408
|
+
if (regex$1.test(line)) results.push(`${filePath}:${i + 1}:${line}`);
|
|
30409
|
+
}
|
|
30410
|
+
} catch {}
|
|
30411
|
+
}
|
|
30412
|
+
async function scanPath(p) {
|
|
30413
|
+
try {
|
|
30414
|
+
const entries = await readdir$1(p, { withFileTypes: true });
|
|
30415
|
+
for (const entry of entries) {
|
|
30416
|
+
const full = join$1(p, entry.name);
|
|
30417
|
+
if (entry.isDirectory()) await scanPath(full);
|
|
30418
|
+
else await scanFile(full);
|
|
30419
|
+
}
|
|
30420
|
+
} catch {
|
|
30421
|
+
await scanFile(p);
|
|
30422
|
+
}
|
|
30423
|
+
}
|
|
30424
|
+
for (const p of paths) await scanPath(resolve$1(p));
|
|
30425
|
+
return results.join("\n");
|
|
30426
|
+
}
|
|
30427
|
+
function createGlobTool() {
|
|
30428
|
+
return {
|
|
30429
|
+
name: "glob",
|
|
30430
|
+
description: "Find files matching a glob pattern. Returns newline-separated matching paths.",
|
|
30431
|
+
inputSchema: {
|
|
30432
|
+
type: "object",
|
|
30433
|
+
properties: { pattern: {
|
|
30434
|
+
type: "string",
|
|
30435
|
+
description: "Glob pattern to match files against"
|
|
30436
|
+
} },
|
|
30437
|
+
required: ["pattern"]
|
|
30438
|
+
},
|
|
30439
|
+
outputTruncation: 5e3,
|
|
30440
|
+
async executor(args, env) {
|
|
30441
|
+
return manualGlob(args.pattern, env);
|
|
30442
|
+
}
|
|
30443
|
+
};
|
|
30444
|
+
}
|
|
30445
|
+
async function manualGlob(pattern, env) {
|
|
30446
|
+
try {
|
|
30447
|
+
const result = await env.exec(`find . -path "./${pattern}" 2>/dev/null || true`, 5e3);
|
|
30448
|
+
return result.stdout.trim();
|
|
30449
|
+
} catch {
|
|
30450
|
+
return "";
|
|
30451
|
+
}
|
|
30452
|
+
}
|
|
30453
|
+
|
|
30454
|
+
//#endregion
|
|
30455
|
+
//#region packages/factory/dist/agent/tools/anthropic-tools.js
|
|
30456
|
+
/**
|
|
30457
|
+
* Creates the edit_file tool used by Anthropic (Claude) models.
|
|
30458
|
+
* Performs exact string search-and-replace on file contents.
|
|
30459
|
+
*/
|
|
30460
|
+
function createEditFileTool() {
|
|
30461
|
+
return {
|
|
30462
|
+
name: "edit_file",
|
|
30463
|
+
description: "Edit a file by replacing an exact string. The old_string must appear exactly once in the file. Provide enough context to uniquely identify the target location.",
|
|
30464
|
+
inputSchema: {
|
|
30465
|
+
type: "object",
|
|
30466
|
+
properties: {
|
|
30467
|
+
path: {
|
|
30468
|
+
type: "string",
|
|
30469
|
+
description: "Path to the file to edit"
|
|
30470
|
+
},
|
|
30471
|
+
old_string: {
|
|
30472
|
+
type: "string",
|
|
30473
|
+
description: "Exact string to search for (must be unique in the file)"
|
|
30474
|
+
},
|
|
30475
|
+
new_string: {
|
|
30476
|
+
type: "string",
|
|
30477
|
+
description: "String to replace old_string with"
|
|
30478
|
+
}
|
|
30479
|
+
},
|
|
30480
|
+
required: [
|
|
30481
|
+
"path",
|
|
30482
|
+
"old_string",
|
|
30483
|
+
"new_string"
|
|
30484
|
+
]
|
|
30485
|
+
},
|
|
30486
|
+
async executor(args, _env) {
|
|
30487
|
+
const content = await readFile$1(args.path, "utf-8");
|
|
30488
|
+
let count = 0;
|
|
30489
|
+
let pos = 0;
|
|
30490
|
+
while ((pos = content.indexOf(args.old_string, pos)) !== -1) {
|
|
30491
|
+
count++;
|
|
30492
|
+
pos += args.old_string.length;
|
|
30493
|
+
}
|
|
30494
|
+
if (count === 0) throw new Error("old_string not found in file");
|
|
30495
|
+
if (count > 1) throw new Error(`old_string is ambiguous (found ${count} times)`);
|
|
30496
|
+
const updated = content.replace(args.old_string, args.new_string);
|
|
30497
|
+
await writeFile$1(args.path, updated, "utf-8");
|
|
30498
|
+
return `Edited ${args.path}`;
|
|
30499
|
+
}
|
|
30500
|
+
};
|
|
30501
|
+
}
|
|
30502
|
+
|
|
30503
|
+
//#endregion
|
|
30504
|
+
//#region packages/factory/dist/agent/tools/openai-tools.js
|
|
30505
|
+
/**
|
|
30506
|
+
* Creates the apply_patch tool used by OpenAI models.
|
|
30507
|
+
* Accepts v4a-format patch strings.
|
|
30508
|
+
*/
|
|
30509
|
+
function createApplyPatchTool() {
|
|
30510
|
+
return {
|
|
30511
|
+
name: "apply_patch",
|
|
30512
|
+
description: "Apply a v4a-format patch to modify files. Format: *** Begin Patch\\n*** Update File: <path>\\n@@ <context>\\n-<removed line>\\n+<added line>\\n*** End Patch",
|
|
30513
|
+
inputSchema: {
|
|
30514
|
+
type: "object",
|
|
30515
|
+
properties: { patch: {
|
|
30516
|
+
type: "string",
|
|
30517
|
+
description: "v4a format patch string. Uses *** Begin Patch / *** End Patch delimiters, *** Update File: <path> or *** Add File: <path> headers, @@ hunk markers, and -/+ diff lines."
|
|
30518
|
+
} },
|
|
30519
|
+
required: ["patch"]
|
|
30520
|
+
},
|
|
30521
|
+
async executor(args, env) {
|
|
30522
|
+
return applyV4aPatch(args.patch, env.workdir);
|
|
30523
|
+
}
|
|
30524
|
+
};
|
|
30525
|
+
}
|
|
30526
|
+
/**
|
|
30527
|
+
* Applies a v4a-format patch string to the filesystem.
|
|
30528
|
+
* Exported as a pure helper for independent testing.
|
|
30529
|
+
*/
|
|
30530
|
+
async function applyV4aPatch(patch, workdir) {
|
|
30531
|
+
if (!patch.includes("*** Begin Patch")) throw new Error("Malformed patch: missing *** Begin Patch delimiter");
|
|
30532
|
+
if (!patch.includes("*** End Patch")) throw new Error("Malformed patch: missing *** End Patch delimiter");
|
|
30533
|
+
const lines = patch.split("\n");
|
|
30534
|
+
const changedFiles = [];
|
|
30535
|
+
let i = 0;
|
|
30536
|
+
while (i < lines.length && !(lines[i] ?? "").includes("*** Begin Patch")) i++;
|
|
30537
|
+
i++;
|
|
30538
|
+
while (i < lines.length) {
|
|
30539
|
+
const line = lines[i] ?? "";
|
|
30540
|
+
if (line.includes("*** End Patch")) break;
|
|
30541
|
+
if (line.startsWith("*** Update File: ")) {
|
|
30542
|
+
const filePath = line.slice(17).trim();
|
|
30543
|
+
const absPath = join$1(workdir, filePath);
|
|
30544
|
+
i++;
|
|
30545
|
+
i = await applyUpdateBlock(lines, i, absPath);
|
|
30546
|
+
changedFiles.push(filePath);
|
|
30547
|
+
} else if (line.startsWith("*** Add File: ")) {
|
|
30548
|
+
const filePath = line.slice(14).trim();
|
|
30549
|
+
const absPath = join$1(workdir, filePath);
|
|
30550
|
+
i++;
|
|
30551
|
+
i = await applyAddBlock(lines, i, absPath);
|
|
30552
|
+
changedFiles.push(filePath);
|
|
30553
|
+
} else i++;
|
|
30554
|
+
}
|
|
30555
|
+
if (changedFiles.length === 0) throw new Error("Malformed patch: no file operations found");
|
|
30556
|
+
return `Applied patch to: ${changedFiles.join(", ")}`;
|
|
30557
|
+
}
|
|
30558
|
+
/**
|
|
30559
|
+
* Apply an update block (hunks with +/- lines) to an existing file.
|
|
30560
|
+
* Returns the next line index after the block.
|
|
30561
|
+
*/
|
|
30562
|
+
async function applyUpdateBlock(lines, startIdx, absPath) {
|
|
30563
|
+
let content;
|
|
30564
|
+
try {
|
|
30565
|
+
content = await readFile$1(absPath, "utf-8");
|
|
30566
|
+
} catch {
|
|
30567
|
+
throw new Error(`File not found: ${absPath}`);
|
|
30568
|
+
}
|
|
30569
|
+
const contentLines = content.split("\n");
|
|
30570
|
+
let i = startIdx;
|
|
30571
|
+
while (i < lines.length) {
|
|
30572
|
+
const line = lines[i] ?? "";
|
|
30573
|
+
if (line.startsWith("*** Update File: ") || line.startsWith("*** Add File: ") || line.includes("*** End Patch")) break;
|
|
30574
|
+
if (line.startsWith("@@")) {
|
|
30575
|
+
i++;
|
|
30576
|
+
const removals = [];
|
|
30577
|
+
const additions = [];
|
|
30578
|
+
const context = [];
|
|
30579
|
+
while (i < lines.length) {
|
|
30580
|
+
const hunkLine = lines[i] ?? "";
|
|
30581
|
+
if (hunkLine.startsWith("@@") || hunkLine.startsWith("*** Update File: ") || hunkLine.startsWith("*** Add File: ") || hunkLine.includes("*** End Patch")) break;
|
|
30582
|
+
if (hunkLine.startsWith("-")) removals.push(hunkLine.slice(1));
|
|
30583
|
+
else if (hunkLine.startsWith("+")) additions.push(hunkLine.slice(1));
|
|
30584
|
+
else context.push(hunkLine.startsWith(" ") ? hunkLine.slice(1) : hunkLine);
|
|
30585
|
+
i++;
|
|
30586
|
+
}
|
|
30587
|
+
if (removals.length > 0) applyHunk(contentLines, removals, additions, context);
|
|
30588
|
+
else if (additions.length > 0) applyAdditionHunk(contentLines, additions, context);
|
|
30589
|
+
} else i++;
|
|
30590
|
+
}
|
|
30591
|
+
await writeFile$1(absPath, contentLines.join("\n"), "utf-8");
|
|
30592
|
+
return i;
|
|
30593
|
+
}
|
|
30594
|
+
/**
|
|
30595
|
+
* Apply a hunk with removals to contentLines (in-place mutation).
|
|
30596
|
+
*/
|
|
30597
|
+
function applyHunk(contentLines, removals, additions, _context) {
|
|
30598
|
+
const firstRemoval = removals[0] ?? "";
|
|
30599
|
+
const startIdx = contentLines.findIndex((l) => l === firstRemoval);
|
|
30600
|
+
if (startIdx === -1) {
|
|
30601
|
+
const trimIdx = contentLines.findIndex((l) => l.trim() === firstRemoval.trim());
|
|
30602
|
+
if (trimIdx === -1) return;
|
|
30603
|
+
contentLines.splice(trimIdx, removals.length, ...additions);
|
|
30604
|
+
return;
|
|
30605
|
+
}
|
|
30606
|
+
contentLines.splice(startIdx, removals.length, ...additions);
|
|
30607
|
+
}
|
|
30608
|
+
/**
|
|
30609
|
+
* Apply a pure addition hunk (no removals) — insert after context.
|
|
30610
|
+
*/
|
|
30611
|
+
function applyAdditionHunk(contentLines, additions, context) {
|
|
30612
|
+
if (context.length === 0) {
|
|
30613
|
+
contentLines.push(...additions);
|
|
30614
|
+
return;
|
|
30615
|
+
}
|
|
30616
|
+
const lastCtx = context[context.length - 1] ?? "";
|
|
30617
|
+
let idx = -1;
|
|
30618
|
+
for (let j$1 = contentLines.length - 1; j$1 >= 0; j$1--) if (contentLines[j$1] === lastCtx) {
|
|
30619
|
+
idx = j$1;
|
|
30620
|
+
break;
|
|
30621
|
+
}
|
|
30622
|
+
if (idx === -1) {
|
|
30623
|
+
contentLines.push(...additions);
|
|
30624
|
+
return;
|
|
30625
|
+
}
|
|
30626
|
+
contentLines.splice(idx + 1, 0, ...additions);
|
|
30627
|
+
}
|
|
30628
|
+
/**
|
|
30629
|
+
* Apply an add block (new file creation) from patch lines.
|
|
30630
|
+
* Returns the next line index after the block.
|
|
30631
|
+
*/
|
|
30632
|
+
async function applyAddBlock(lines, startIdx, absPath) {
|
|
30633
|
+
let i = startIdx;
|
|
30634
|
+
const fileLines = [];
|
|
30635
|
+
while (i < lines.length) {
|
|
30636
|
+
const line = lines[i] ?? "";
|
|
30637
|
+
if (line.startsWith("*** Update File: ") || line.startsWith("*** Add File: ") || line.includes("*** End Patch")) break;
|
|
30638
|
+
if (line.startsWith("+")) fileLines.push(line.slice(1));
|
|
30639
|
+
i++;
|
|
30640
|
+
}
|
|
30641
|
+
await mkdir$1(dirname$1(absPath), { recursive: true });
|
|
30642
|
+
await writeFile$1(absPath, fileLines.join("\n"), "utf-8");
|
|
30643
|
+
return i;
|
|
30644
|
+
}
|
|
30645
|
+
|
|
30646
|
+
//#endregion
|
|
30647
|
+
//#region packages/factory/dist/agent/tools/gemini-tools.js
|
|
30648
|
+
/**
|
|
30649
|
+
* Creates the read_many_files tool for Gemini models.
|
|
30650
|
+
* Reads multiple files and returns concatenated content with headers.
|
|
30651
|
+
*/
|
|
30652
|
+
function createReadManyFilesTool() {
|
|
30653
|
+
return {
|
|
30654
|
+
name: "read_many_files",
|
|
30655
|
+
description: "Read multiple files and return their concatenated content with file headers.",
|
|
30656
|
+
inputSchema: {
|
|
30657
|
+
type: "object",
|
|
30658
|
+
properties: { paths: {
|
|
30659
|
+
type: "array",
|
|
30660
|
+
items: { type: "string" },
|
|
30661
|
+
description: "Array of file paths to read"
|
|
30662
|
+
} },
|
|
30663
|
+
required: ["paths"]
|
|
30664
|
+
},
|
|
30665
|
+
outputTruncation: 1e5,
|
|
30666
|
+
async executor(args, _env) {
|
|
30667
|
+
const parts = [];
|
|
30668
|
+
for (const filePath of args.paths) try {
|
|
30669
|
+
const content = await readFile$1(filePath, "utf-8");
|
|
30670
|
+
const lines = content.split("\n");
|
|
30671
|
+
const numbered = lines.map((line, i) => `${String(i + 1).padStart(3)}\t${line}`).join("\n");
|
|
30672
|
+
parts.push(`=== ${filePath} ===\n${numbered}`);
|
|
30673
|
+
} catch (err) {
|
|
30674
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30675
|
+
parts.push(`=== ${filePath} ===\n(error reading file: ${message})`);
|
|
30676
|
+
}
|
|
30677
|
+
return parts.join("\n\n");
|
|
30678
|
+
}
|
|
30679
|
+
};
|
|
30680
|
+
}
|
|
30681
|
+
/**
|
|
30682
|
+
* Creates the list_dir tool for Gemini models.
|
|
30683
|
+
* Returns a directory listing with entry types (dirs first, then alpha).
|
|
30684
|
+
*/
|
|
30685
|
+
function createListDirTool() {
|
|
30686
|
+
return {
|
|
30687
|
+
name: "list_dir",
|
|
30688
|
+
description: "List directory contents with entry types ([DIR] or [FILE]), sorted dirs first then alphabetically.",
|
|
30689
|
+
inputSchema: {
|
|
30690
|
+
type: "object",
|
|
30691
|
+
properties: { path: {
|
|
30692
|
+
type: "string",
|
|
30693
|
+
description: "Directory path to list"
|
|
30694
|
+
} },
|
|
30695
|
+
required: ["path"]
|
|
30696
|
+
},
|
|
30697
|
+
async executor(args, _env) {
|
|
30698
|
+
const entries = await readdir$1(args.path, { withFileTypes: true });
|
|
30699
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => `[DIR] ${e.name}`).sort();
|
|
30700
|
+
const files = entries.filter((e) => !e.isDirectory()).map((e) => `[FILE] ${e.name}`).sort();
|
|
30701
|
+
return [...dirs, ...files].join("\n");
|
|
30702
|
+
}
|
|
30703
|
+
};
|
|
30704
|
+
}
|
|
30705
|
+
/**
|
|
30706
|
+
* Creates the Gemini variant of edit_file.
|
|
30707
|
+
* Uses file_path (instead of path) as the parameter name to match gemini-cli conventions.
|
|
30708
|
+
*/
|
|
30709
|
+
function createGeminiEditFileTool() {
|
|
30710
|
+
return {
|
|
30711
|
+
name: "edit_file",
|
|
30712
|
+
description: "Edit a file by replacing an exact string. The old_string must appear exactly once in the file.",
|
|
30713
|
+
inputSchema: {
|
|
30714
|
+
type: "object",
|
|
30715
|
+
properties: {
|
|
30716
|
+
file_path: {
|
|
30717
|
+
type: "string",
|
|
30718
|
+
description: "Path to the file to edit"
|
|
30719
|
+
},
|
|
30720
|
+
old_string: {
|
|
30721
|
+
type: "string",
|
|
30722
|
+
description: "Exact string to search for (must be unique in the file)"
|
|
30723
|
+
},
|
|
30724
|
+
new_string: {
|
|
30725
|
+
type: "string",
|
|
30726
|
+
description: "String to replace old_string with"
|
|
30727
|
+
}
|
|
30728
|
+
},
|
|
30729
|
+
required: [
|
|
30730
|
+
"file_path",
|
|
30731
|
+
"old_string",
|
|
30732
|
+
"new_string"
|
|
30733
|
+
]
|
|
30734
|
+
},
|
|
30735
|
+
async executor(args, _env) {
|
|
30736
|
+
const content = await readFile$1(args.file_path, "utf-8");
|
|
30737
|
+
let count = 0;
|
|
30738
|
+
let pos = 0;
|
|
30739
|
+
while ((pos = content.indexOf(args.old_string, pos)) !== -1) {
|
|
30740
|
+
count++;
|
|
30741
|
+
pos += args.old_string.length;
|
|
30742
|
+
}
|
|
30743
|
+
if (count === 0) throw new Error("old_string not found in file");
|
|
30744
|
+
if (count > 1) throw new Error(`old_string is ambiguous (found ${count} times)`);
|
|
30745
|
+
const updated = content.replace(args.old_string, args.new_string);
|
|
30746
|
+
await mkdir$1(dirname$1(args.file_path), { recursive: true });
|
|
30747
|
+
await writeFile$1(args.file_path, updated, "utf-8");
|
|
30748
|
+
return `Edited ${args.file_path}`;
|
|
30749
|
+
}
|
|
30750
|
+
};
|
|
30751
|
+
}
|
|
30752
|
+
|
|
30753
|
+
//#endregion
|
|
30754
|
+
//#region packages/factory/dist/agent/tools/profiles.js
|
|
30755
|
+
/**
|
|
30756
|
+
* Anthropic (Claude) provider profile.
|
|
30757
|
+
* Uses edit_file for exact string search-and-replace.
|
|
30758
|
+
*/
|
|
30759
|
+
var AnthropicProfile = class {
|
|
30760
|
+
model;
|
|
30761
|
+
id = "anthropic";
|
|
30762
|
+
supports_streaming = true;
|
|
30763
|
+
context_window_size = 2e5;
|
|
30764
|
+
supports_parallel_tool_calls = true;
|
|
30765
|
+
constructor(model) {
|
|
30766
|
+
this.model = model;
|
|
30767
|
+
}
|
|
30768
|
+
build_system_prompt() {
|
|
30769
|
+
return [
|
|
30770
|
+
"You are a coding agent. You have access to tools for reading and modifying files, running shell commands, and searching code.",
|
|
30771
|
+
"",
|
|
30772
|
+
"Available tools:",
|
|
30773
|
+
"- read_file: Read file contents with optional line range (offset/limit)",
|
|
30774
|
+
"- write_file: Write content to a file (creates parent dirs as needed)",
|
|
30775
|
+
"- edit_file: Replace an exact string in a file (old_string must be unique)",
|
|
30776
|
+
"- shell: Execute shell commands",
|
|
30777
|
+
"- grep: Search for patterns in files",
|
|
30778
|
+
"- glob: Find files matching a pattern",
|
|
30779
|
+
"",
|
|
30780
|
+
"Guidelines:",
|
|
30781
|
+
"- Always read files before editing them to understand their current content",
|
|
30782
|
+
"- Use edit_file for targeted changes, write_file for full rewrites",
|
|
30783
|
+
"- Prefer targeted edits over full file rewrites when possible"
|
|
30784
|
+
].join("\n");
|
|
30785
|
+
}
|
|
30786
|
+
tools() {
|
|
30787
|
+
return [...createSharedTools(12e4), createEditFileTool()];
|
|
30788
|
+
}
|
|
30789
|
+
provider_options() {
|
|
30790
|
+
return { max_tokens: 4096 };
|
|
30791
|
+
}
|
|
30792
|
+
};
|
|
30793
|
+
/**
|
|
30794
|
+
* OpenAI provider profile.
|
|
30795
|
+
* Uses apply_patch for v4a-format patch application.
|
|
30796
|
+
* Does NOT include edit_file.
|
|
30797
|
+
*/
|
|
30798
|
+
var OpenAIProfile = class {
|
|
30799
|
+
model;
|
|
30800
|
+
id = "openai";
|
|
30801
|
+
supports_streaming = true;
|
|
30802
|
+
context_window_size = 128e3;
|
|
30803
|
+
supports_parallel_tool_calls = true;
|
|
30804
|
+
constructor(model) {
|
|
30805
|
+
this.model = model;
|
|
30806
|
+
}
|
|
30807
|
+
build_system_prompt() {
|
|
30808
|
+
return [
|
|
30809
|
+
"You are a coding agent. You have access to tools for reading and modifying files, running shell commands, and searching code.",
|
|
30810
|
+
"",
|
|
30811
|
+
"Available tools:",
|
|
30812
|
+
"- read_file: Read file contents with optional line range",
|
|
30813
|
+
"- write_file: Write content to a file",
|
|
30814
|
+
"- apply_patch: Apply a v4a-format patch to modify files",
|
|
30815
|
+
"- shell: Execute shell commands",
|
|
30816
|
+
"- grep: Search for patterns in files",
|
|
30817
|
+
"- glob: Find files matching a pattern"
|
|
30818
|
+
].join("\n");
|
|
30819
|
+
}
|
|
30820
|
+
tools() {
|
|
30821
|
+
return [...createSharedTools(1e4), createApplyPatchTool()];
|
|
30822
|
+
}
|
|
30823
|
+
provider_options() {
|
|
30824
|
+
return {};
|
|
30825
|
+
}
|
|
30826
|
+
};
|
|
30827
|
+
/**
|
|
30828
|
+
* Gemini provider profile.
|
|
30829
|
+
* Uses read_many_files, list_dir, and a Gemini-specific edit_file variant.
|
|
30830
|
+
*/
|
|
30831
|
+
var GeminiProfile = class {
|
|
30832
|
+
model;
|
|
30833
|
+
id = "gemini";
|
|
30834
|
+
supports_streaming = true;
|
|
30835
|
+
context_window_size = 1e6;
|
|
30836
|
+
supports_parallel_tool_calls = true;
|
|
30837
|
+
constructor(model) {
|
|
30838
|
+
this.model = model;
|
|
30839
|
+
}
|
|
30840
|
+
build_system_prompt() {
|
|
30841
|
+
return [
|
|
30842
|
+
"You are a coding agent. You have access to tools for reading and modifying files, running shell commands, and searching code.",
|
|
30843
|
+
"",
|
|
30844
|
+
"Available tools:",
|
|
30845
|
+
"- read_file: Read a single file with optional line range",
|
|
30846
|
+
"- read_many_files: Read multiple files at once",
|
|
30847
|
+
"- write_file: Write content to a file",
|
|
30848
|
+
"- edit_file: Replace an exact string in a file",
|
|
30849
|
+
"- list_dir: List directory contents",
|
|
30850
|
+
"- shell: Execute shell commands",
|
|
30851
|
+
"- grep: Search for patterns in files",
|
|
30852
|
+
"- glob: Find files matching a pattern"
|
|
30853
|
+
].join("\n");
|
|
30854
|
+
}
|
|
30855
|
+
tools() {
|
|
30856
|
+
return [
|
|
30857
|
+
...createSharedTools(1e4),
|
|
30858
|
+
createReadManyFilesTool(),
|
|
30859
|
+
createListDirTool(),
|
|
30860
|
+
createGeminiEditFileTool()
|
|
30861
|
+
];
|
|
30862
|
+
}
|
|
30863
|
+
provider_options() {
|
|
30864
|
+
return {};
|
|
30865
|
+
}
|
|
30866
|
+
};
|
|
30867
|
+
|
|
30868
|
+
//#endregion
|
|
30869
|
+
//#region packages/factory/dist/backend/direct-bootstrap.js
|
|
30870
|
+
/**
|
|
30871
|
+
* Bootstrap a DirectCodergenBackend for the given provider and model.
|
|
30872
|
+
*
|
|
30873
|
+
* Reads the required API key from the environment, instantiates the correct
|
|
30874
|
+
* provider adapter and profile, constructs an LLMClient, and returns a
|
|
30875
|
+
* DirectCodergenBackend configured with the given options.
|
|
30876
|
+
*
|
|
30877
|
+
* @throws Error if the required API key environment variable is missing
|
|
30878
|
+
* @throws Error if the provider string is not recognised
|
|
30879
|
+
*/
|
|
30880
|
+
function bootstrapDirectBackend(opts) {
|
|
30881
|
+
const { provider, model, maxTurns, projectDir, onEvent } = opts;
|
|
30882
|
+
const executionEnv = {
|
|
30883
|
+
workdir: projectDir,
|
|
30884
|
+
exec: (command, timeoutMs) => {
|
|
30885
|
+
return new Promise((resolve$6) => {
|
|
30886
|
+
try {
|
|
30887
|
+
const stdout = execSync(command, {
|
|
30888
|
+
cwd: projectDir,
|
|
30889
|
+
timeout: timeoutMs,
|
|
30890
|
+
encoding: "utf-8"
|
|
30891
|
+
});
|
|
30892
|
+
resolve$6({
|
|
30893
|
+
stdout: stdout || "",
|
|
30894
|
+
stderr: "",
|
|
30895
|
+
exitCode: 0
|
|
30896
|
+
});
|
|
30897
|
+
} catch (err) {
|
|
30898
|
+
const execError = err;
|
|
30899
|
+
resolve$6({
|
|
30900
|
+
stdout: execError.stdout ? String(execError.stdout) : "",
|
|
30901
|
+
stderr: execError.stderr ? String(execError.stderr) : "",
|
|
30902
|
+
exitCode: typeof execError.status === "number" ? execError.status : 1
|
|
30903
|
+
});
|
|
30904
|
+
}
|
|
30905
|
+
});
|
|
30906
|
+
}
|
|
30907
|
+
};
|
|
30908
|
+
const client = new LLMClient();
|
|
30909
|
+
if (provider === "anthropic") {
|
|
30910
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
30911
|
+
if (!apiKey) throw new Error("ANTHROPIC_API_KEY environment variable is required for direct backend with anthropic provider");
|
|
30912
|
+
const adapter = new AnthropicAdapter({ apiKey });
|
|
30913
|
+
client.registerProvider("anthropic", adapter);
|
|
30914
|
+
client.registerModelPattern("claude-*", "anthropic");
|
|
30915
|
+
const providerProfile = new AnthropicProfile(model);
|
|
30916
|
+
return createDirectCodergenBackend({
|
|
30917
|
+
llmClient: client,
|
|
30918
|
+
providerProfile,
|
|
30919
|
+
executionEnv,
|
|
30920
|
+
config: { max_turns: maxTurns },
|
|
30921
|
+
...onEvent !== void 0 ? { onEvent } : {}
|
|
30922
|
+
});
|
|
30923
|
+
}
|
|
30924
|
+
if (provider === "openai") {
|
|
30925
|
+
const apiKey = process.env["OPENAI_API_KEY"];
|
|
30926
|
+
if (!apiKey) throw new Error("OPENAI_API_KEY environment variable is required for direct backend with openai provider");
|
|
30927
|
+
const adapter = new OpenAIAdapter({ apiKey });
|
|
30928
|
+
client.registerProvider("openai", adapter);
|
|
30929
|
+
client.registerModelPattern("gpt-*", "openai");
|
|
30930
|
+
const providerProfile = new OpenAIProfile(model);
|
|
30931
|
+
return createDirectCodergenBackend({
|
|
30932
|
+
llmClient: client,
|
|
30933
|
+
providerProfile,
|
|
30934
|
+
executionEnv,
|
|
30935
|
+
config: { max_turns: maxTurns },
|
|
30936
|
+
...onEvent !== void 0 ? { onEvent } : {}
|
|
30937
|
+
});
|
|
30938
|
+
}
|
|
30939
|
+
if (provider === "gemini") {
|
|
30940
|
+
const apiKey = process.env["GEMINI_API_KEY"];
|
|
30941
|
+
if (!apiKey) throw new Error("GEMINI_API_KEY environment variable is required for direct backend with gemini provider");
|
|
30942
|
+
const adapter = new GeminiAdapter({ apiKey });
|
|
30943
|
+
client.registerProvider("gemini", adapter);
|
|
30944
|
+
client.registerModelPattern("gemini-*", "gemini");
|
|
30945
|
+
const providerProfile = new GeminiProfile(model);
|
|
30946
|
+
return createDirectCodergenBackend({
|
|
30947
|
+
llmClient: client,
|
|
30948
|
+
providerProfile,
|
|
30949
|
+
executionEnv,
|
|
30950
|
+
config: { max_turns: maxTurns },
|
|
30951
|
+
...onEvent !== void 0 ? { onEvent } : {}
|
|
30952
|
+
});
|
|
30953
|
+
}
|
|
30954
|
+
throw new Error(`Unknown direct backend provider: ${provider}`);
|
|
30955
|
+
}
|
|
30956
|
+
|
|
30957
|
+
//#endregion
|
|
30958
|
+
//#region packages/factory/dist/handlers/start.js
|
|
30959
|
+
/**
|
|
30960
|
+
* Handler for start nodes.
|
|
30961
|
+
* No side effects — edge selection drives the next step.
|
|
30962
|
+
*
|
|
30963
|
+
* Story 42-9.
|
|
30964
|
+
*/
|
|
30965
|
+
/** Handler for start nodes; no side effects; edge selection drives next step. */
|
|
30966
|
+
const startHandler = async (_node, _context, _graph) => {
|
|
30967
|
+
return { status: "SUCCESS" };
|
|
30968
|
+
};
|
|
30969
|
+
|
|
30970
|
+
//#endregion
|
|
30971
|
+
//#region packages/factory/dist/handlers/exit.js
|
|
30972
|
+
/**
|
|
30973
|
+
* Handler for exit/terminal nodes.
|
|
30974
|
+
* Signals successful graph completion with no side effects.
|
|
30975
|
+
*
|
|
30976
|
+
* Story 42-9.
|
|
30977
|
+
*/
|
|
30978
|
+
/** Handler for exit/terminal nodes; signals successful graph completion. */
|
|
30979
|
+
const exitHandler = async (_node, _context, _graph) => {
|
|
30980
|
+
return { status: "SUCCESS" };
|
|
30981
|
+
};
|
|
30982
|
+
|
|
30983
|
+
//#endregion
|
|
30984
|
+
//#region packages/factory/dist/handlers/conditional.js
|
|
30985
|
+
/**
|
|
30986
|
+
* Handler for conditional/branching nodes.
|
|
30987
|
+
* Routing is delegated entirely to edge selection (story 42-12).
|
|
30988
|
+
* This handler does nothing — it simply returns SUCCESS so the executor
|
|
30989
|
+
* can proceed to edge evaluation.
|
|
30990
|
+
*
|
|
30991
|
+
* Story 42-9.
|
|
30992
|
+
*/
|
|
30993
|
+
/**
|
|
30994
|
+
* Handler for conditional/branching nodes; routing is delegated entirely to
|
|
30995
|
+
* edge selection (story 42-12); this handler does nothing.
|
|
30996
|
+
*/
|
|
30997
|
+
const conditionalHandler = async (_node, _context, _graph) => {
|
|
30998
|
+
return { status: "SUCCESS" };
|
|
30999
|
+
};
|
|
31000
|
+
|
|
31001
|
+
//#endregion
|
|
31002
|
+
//#region packages/factory/dist/stylesheet/resolver.js
|
|
31003
|
+
/**
|
|
31004
|
+
* Specificity-based resolver for the model stylesheet.
|
|
31005
|
+
*
|
|
31006
|
+
* Given a parsed stylesheet and a graph node, `resolveNodeStyles` determines
|
|
31007
|
+
* which LLM routing properties apply to that node by:
|
|
31008
|
+
* 1. Filtering rules to only those whose selector matches the node.
|
|
31009
|
+
* 2. Sorting matching rules by specificity ascending (stable sort so that
|
|
31010
|
+
* equal-specificity rules preserve source order).
|
|
31011
|
+
* 3. Iterating the sorted list and letting each rule overwrite properties
|
|
31012
|
+
* — the last rule at the highest specificity wins; for ties the rule
|
|
31013
|
+
* appearing later in the original stylesheet wins.
|
|
31014
|
+
*
|
|
31015
|
+
* **Caller contract**: `resolveNodeStyles` does NOT enforce the "explicit node
|
|
31016
|
+
* attribute wins" rule. The caller (executor / node preparation layer) is
|
|
31017
|
+
* responsible for the final merge:
|
|
31018
|
+
* ```typescript
|
|
31019
|
+
* const resolved = resolveNodeStyles(node, stylesheet)
|
|
31020
|
+
* const finalModel = node.llmModel || resolved.llmModel || graph.defaultLlmModel || ''
|
|
31021
|
+
* ```
|
|
31022
|
+
*
|
|
31023
|
+
* Story 42-7.
|
|
31024
|
+
*/
|
|
31025
|
+
/**
|
|
31026
|
+
* Return `true` if the given `selector` matches the provided `node`.
|
|
31027
|
+
*
|
|
31028
|
+
* Matching rules:
|
|
31029
|
+
* - `universal`: always matches every node.
|
|
31030
|
+
* - `shape`: matches when `node.shape === selector.value`.
|
|
31031
|
+
* - `class`: matches when the node's `class` field, split on commas and
|
|
31032
|
+
* trimmed, contains `selector.value` (case-sensitive).
|
|
31033
|
+
* - `id`: matches when `node.id === selector.value`.
|
|
31034
|
+
*/
|
|
31035
|
+
function matchesNode(node, selector) {
|
|
31036
|
+
switch (selector.type) {
|
|
31037
|
+
case "universal": return true;
|
|
31038
|
+
case "shape": return node.shape === selector.value;
|
|
31039
|
+
case "class": {
|
|
31040
|
+
const tokens = node.class.split(",").map((t) => t.trim());
|
|
31041
|
+
return tokens.includes(selector.value);
|
|
31042
|
+
}
|
|
31043
|
+
case "id": return node.id === selector.value;
|
|
31044
|
+
default: return false;
|
|
31045
|
+
}
|
|
31046
|
+
}
|
|
31047
|
+
/**
|
|
31048
|
+
* Resolve LLM routing properties for a single graph node using the parsed stylesheet.
|
|
31049
|
+
*
|
|
31050
|
+
* @param node - The graph node to resolve properties for.
|
|
31051
|
+
* @param stylesheet - A parsed stylesheet (array of `StylesheetRule` in source order).
|
|
31052
|
+
* @returns A `ResolvedNodeStyles` object containing only the properties that
|
|
31053
|
+
* were resolved from matching rules. Properties absent from matching rules
|
|
31054
|
+
* are omitted (not set to `undefined` explicitly).
|
|
31055
|
+
*
|
|
31056
|
+
* **Important**: this function does NOT check the node's own attributes.
|
|
31057
|
+
* Callers must give explicit node attributes (`node.llmModel`, etc.) priority
|
|
31058
|
+
* over the values returned here.
|
|
31059
|
+
*/
|
|
31060
|
+
function resolveNodeStyles(node, stylesheet) {
|
|
31061
|
+
const matchingRules = stylesheet.filter((rule) => matchesNode(node, rule.selector));
|
|
31062
|
+
matchingRules.sort((a, b) => a.selector.specificity - b.selector.specificity);
|
|
31063
|
+
const resolved = {};
|
|
31064
|
+
for (const rule of matchingRules) for (const decl of rule.declarations) if (decl.property === "llm_model") resolved.llmModel = decl.value;
|
|
31065
|
+
else if (decl.property === "llm_provider") resolved.llmProvider = decl.value;
|
|
31066
|
+
else if (decl.property === "reasoning_effort") resolved.reasoningEffort = decl.value;
|
|
31067
|
+
return resolved;
|
|
31068
|
+
}
|
|
31069
|
+
|
|
31070
|
+
//#endregion
|
|
31071
|
+
//#region packages/factory/dist/handlers/codergen-handler.js
|
|
31072
|
+
const DEFAULT_MODEL = "claude-sonnet-4-5";
|
|
31073
|
+
const DEFAULT_PROVIDER = "anthropic";
|
|
31074
|
+
const DEFAULT_REASONING_EFFORT = "medium";
|
|
31075
|
+
/**
|
|
31076
|
+
* Replace all `{{key}}` placeholders in `template` with the corresponding
|
|
31077
|
+
* values from `context`. Missing keys resolve to empty string `""` without
|
|
31078
|
+
* throwing.
|
|
31079
|
+
*
|
|
31080
|
+
* @param template - The prompt template string (may contain `{{variable}}` tokens).
|
|
31081
|
+
* @param context - The graph context to read values from.
|
|
31082
|
+
* @returns The fully-interpolated prompt string.
|
|
31083
|
+
*/
|
|
31084
|
+
function interpolatePrompt(template, context) {
|
|
31085
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
31086
|
+
return context.getString(key, "");
|
|
31087
|
+
});
|
|
31088
|
+
}
|
|
31089
|
+
/**
|
|
31090
|
+
* Resolve the LLM routing properties for a graph node.
|
|
31091
|
+
*
|
|
31092
|
+
* Priority (highest wins):
|
|
31093
|
+
* 1. Node-level attributes (`node.llmModel`, `node.llmProvider`, `node.reasoningEffort`)
|
|
31094
|
+
* 2. Stylesheet-resolved values via `resolveNodeStyles`
|
|
31095
|
+
* 3. Per-option defaults (`options.defaultModel`, etc.)
|
|
31096
|
+
* 4. System defaults (`claude-sonnet-4-5`, `anthropic`, `medium`)
|
|
31097
|
+
*/
|
|
31098
|
+
function resolveModel(node, stylesheet, options) {
|
|
31099
|
+
const stylesheetResolved = stylesheet ? resolveNodeStyles(node, stylesheet) : {};
|
|
31100
|
+
const llm_model = node.llmModel || stylesheetResolved.llmModel || options?.defaultModel || DEFAULT_MODEL;
|
|
31101
|
+
const llm_provider = node.llmProvider || stylesheetResolved.llmProvider || options?.defaultProvider || DEFAULT_PROVIDER;
|
|
31102
|
+
const reasoning_effort = node.reasoningEffort || stylesheetResolved.reasoningEffort || options?.defaultReasoningEffort || DEFAULT_REASONING_EFFORT;
|
|
31103
|
+
return {
|
|
31104
|
+
llm_model,
|
|
31105
|
+
llm_provider,
|
|
31106
|
+
reasoning_effort
|
|
31107
|
+
};
|
|
31108
|
+
}
|
|
31109
|
+
/**
|
|
31110
|
+
* Returns `true` for errors that are transient and should trigger a
|
|
31111
|
+
* `NEEDS_RETRY` outcome:
|
|
31112
|
+
* - HTTP status 429 (rate limit)
|
|
31113
|
+
* - Errors whose message contains `"timeout"`, `"ETIMEDOUT"`,
|
|
31114
|
+
* `"ECONNRESET"`, or `"ECONNREFUSED"`
|
|
31115
|
+
*
|
|
31116
|
+
* All other errors are non-transient and map to `FAILURE`.
|
|
31117
|
+
*/
|
|
31118
|
+
function isTransientError(error) {
|
|
31119
|
+
if (error == null) return false;
|
|
31120
|
+
const status = error.status ?? error.statusCode;
|
|
31121
|
+
if (status === 429) return true;
|
|
31122
|
+
const message = error.message;
|
|
31123
|
+
if (typeof message === "string") {
|
|
31124
|
+
if (message.includes("timeout") || message.includes("ETIMEDOUT") || message.includes("ECONNRESET") || message.includes("ECONNREFUSED")) return true;
|
|
31125
|
+
}
|
|
31126
|
+
return false;
|
|
31127
|
+
}
|
|
31128
|
+
/**
|
|
31129
|
+
* Create a codergen node handler configured with the given options.
|
|
31130
|
+
*
|
|
31131
|
+
* @param options - Optional configuration (stylesheet, default model/provider/effort).
|
|
31132
|
+
* @returns A `NodeHandler` that:
|
|
31133
|
+
* 1. Interpolates the node's prompt (or label) against GraphContext.
|
|
31134
|
+
* 2. Resolves LLM routing properties.
|
|
31135
|
+
* 3. Calls the LLM client from `\@substrate-ai/core`.
|
|
31136
|
+
* 4. Returns a `SUCCESS` outcome with the response in `contextUpdates`,
|
|
31137
|
+
* `NEEDS_RETRY` for transient errors, or `FAILURE` for others.
|
|
31138
|
+
*/
|
|
31139
|
+
function createCodergenHandler(options) {
|
|
31140
|
+
return async (node, context, _graph) => {
|
|
31141
|
+
const template = node.prompt || node.label || "";
|
|
31142
|
+
const interpolatedPrompt = interpolatePrompt(template, context);
|
|
31143
|
+
if (options?.backend) return options.backend.run(node, interpolatedPrompt, context);
|
|
31144
|
+
if (node.backend === "direct" && options?.directBackend) return options.directBackend.run(node, interpolatedPrompt, context);
|
|
31145
|
+
const { llm_model, llm_provider, reasoning_effort } = resolveModel(node, options?.stylesheet, options);
|
|
31146
|
+
try {
|
|
31147
|
+
const result = await callLLM({
|
|
31148
|
+
model: llm_model,
|
|
31149
|
+
provider: llm_provider,
|
|
31150
|
+
reasoningEffort: reasoning_effort,
|
|
31151
|
+
prompt: interpolatedPrompt
|
|
31152
|
+
});
|
|
31153
|
+
const responseText = result.text;
|
|
31154
|
+
return {
|
|
31155
|
+
status: "SUCCESS",
|
|
31156
|
+
notes: responseText,
|
|
31157
|
+
contextUpdates: { [`${node.id}_output`]: responseText }
|
|
31158
|
+
};
|
|
31159
|
+
} catch (error) {
|
|
31160
|
+
if (isTransientError(error)) return {
|
|
31161
|
+
status: "NEEDS_RETRY",
|
|
31162
|
+
error
|
|
31163
|
+
};
|
|
31164
|
+
return {
|
|
31165
|
+
status: "FAILURE",
|
|
31166
|
+
error
|
|
31167
|
+
};
|
|
31168
|
+
}
|
|
31169
|
+
};
|
|
31170
|
+
}
|
|
31171
|
+
|
|
31172
|
+
//#endregion
|
|
31173
|
+
//#region packages/factory/dist/handlers/tool.js
|
|
31174
|
+
/**
|
|
31175
|
+
* Duck-typing guard: returns true if `parsed` conforms to the ScenarioRunResult
|
|
31176
|
+
* shape (has summary.total and summary.passed as numbers).
|
|
31177
|
+
*
|
|
28962
31178
|
* Detection is intentionally command-agnostic — it operates on the JSON shape
|
|
28963
31179
|
* alone, not on the value of `node.toolCommand`.
|
|
28964
31180
|
*/
|
|
@@ -29176,19 +31392,19 @@ var HandlerRegistry = class {
|
|
|
29176
31392
|
* `shape=box` nodes, and as the registry-level default for any node that has no
|
|
29177
31393
|
* recognised type or shape mapping.
|
|
29178
31394
|
*/
|
|
29179
|
-
function createDefaultRegistry() {
|
|
31395
|
+
function createDefaultRegistry(options) {
|
|
29180
31396
|
const registry = new HandlerRegistry();
|
|
29181
31397
|
registry.register("start", startHandler);
|
|
29182
31398
|
registry.register("exit", exitHandler);
|
|
29183
31399
|
registry.register("conditional", conditionalHandler);
|
|
29184
|
-
registry.register("codergen", createCodergenHandler());
|
|
31400
|
+
registry.register("codergen", createCodergenHandler(options));
|
|
29185
31401
|
registry.register("tool", createToolHandler());
|
|
29186
31402
|
registry.register("wait.human", createWaitHumanHandler());
|
|
29187
31403
|
registry.registerShape("Mdiamond", "start");
|
|
29188
31404
|
registry.registerShape("Msquare", "exit");
|
|
29189
31405
|
registry.registerShape("diamond", "conditional");
|
|
29190
31406
|
registry.registerShape("box", "codergen");
|
|
29191
|
-
registry.setDefault(createCodergenHandler());
|
|
31407
|
+
registry.setDefault(createCodergenHandler(options));
|
|
29192
31408
|
return registry;
|
|
29193
31409
|
}
|
|
29194
31410
|
|
|
@@ -34491,7 +36707,20 @@ const FactoryConfigSchema = z.object({
|
|
|
34491
36707
|
"dual-signal",
|
|
34492
36708
|
"scenario-primary",
|
|
34493
36709
|
"scenario-only"
|
|
34494
|
-
]).default("dual-signal")
|
|
36710
|
+
]).default("dual-signal"),
|
|
36711
|
+
direct_backend: z.object({
|
|
36712
|
+
provider: z.enum([
|
|
36713
|
+
"anthropic",
|
|
36714
|
+
"openai",
|
|
36715
|
+
"gemini"
|
|
36716
|
+
]).default("anthropic"),
|
|
36717
|
+
model: z.string().default("claude-3-5-sonnet-20241022"),
|
|
36718
|
+
max_turns: z.number().int().min(1).default(20)
|
|
36719
|
+
}).default({
|
|
36720
|
+
provider: "anthropic",
|
|
36721
|
+
model: "claude-3-5-sonnet-20241022",
|
|
36722
|
+
max_turns: 20
|
|
36723
|
+
})
|
|
34495
36724
|
}).strict();
|
|
34496
36725
|
/**
|
|
34497
36726
|
* Extends SubstrateConfigSchema with an optional `factory:` section.
|
|
@@ -34673,7 +36902,7 @@ const TOTAL_RULE_COUNT = 13;
|
|
|
34673
36902
|
function registerFactoryCommand(program, options) {
|
|
34674
36903
|
const factoryCmd = program.command("factory").description("Factory pipeline and scenario management commands");
|
|
34675
36904
|
registerScenariosCommand(factoryCmd);
|
|
34676
|
-
factoryCmd.command("run").description("Execute a DOT graph pipeline").option("--graph <path>", "Path to DOT graph file").option("--config <path>", "Path to config.yaml (default: auto-detect)").option("--events", "Emit NDJSON events to stdout").action(async (opts) => {
|
|
36905
|
+
factoryCmd.command("run").description("Execute a DOT graph pipeline").option("--graph <path>", "Path to DOT graph file").option("--config <path>", "Path to config.yaml (default: auto-detect)").option("--events", "Emit NDJSON events to stdout").option("--backend <mode>", "Backend: cli | direct (overrides config factory.backend)").action(async (opts) => {
|
|
34677
36906
|
try {
|
|
34678
36907
|
const projectDir = process.cwd();
|
|
34679
36908
|
const graphPath = await resolveGraphPath(opts, projectDir);
|
|
@@ -34709,6 +36938,18 @@ function registerFactoryCommand(program, options) {
|
|
|
34709
36938
|
type: "factory:config-reloaded",
|
|
34710
36939
|
...e
|
|
34711
36940
|
}));
|
|
36941
|
+
eventBus.on("agent:tool-call", (e) => emit({
|
|
36942
|
+
type: "agent:tool-call",
|
|
36943
|
+
...e
|
|
36944
|
+
}));
|
|
36945
|
+
eventBus.on("agent:loop-detected", (e) => emit({
|
|
36946
|
+
type: "agent:loop-detected",
|
|
36947
|
+
...e
|
|
36948
|
+
}));
|
|
36949
|
+
eventBus.on("agent:steering-injected", (e) => emit({
|
|
36950
|
+
type: "agent:steering-injected",
|
|
36951
|
+
...e
|
|
36952
|
+
}));
|
|
34712
36953
|
}
|
|
34713
36954
|
process.stdout.write(`Running graph pipeline from ${graphPath}\n`);
|
|
34714
36955
|
const runId = randomUUID();
|
|
@@ -34717,6 +36958,63 @@ function registerFactoryCommand(program, options) {
|
|
|
34717
36958
|
await stateManager.initRun(dotSource);
|
|
34718
36959
|
/** wallClockCapMs: FactoryConfig.wall_clock_cap_seconds × 1000 (story 45-10) */
|
|
34719
36960
|
const factoryConfig = await loadFactoryConfig(projectDir, opts.config);
|
|
36961
|
+
const effectiveBackend = opts.backend ?? factoryConfig.factory?.backend ?? "cli";
|
|
36962
|
+
let directBackend;
|
|
36963
|
+
if (effectiveBackend === "direct") {
|
|
36964
|
+
let currentNodeId = "";
|
|
36965
|
+
eventBus.on("graph:node-started", (e) => {
|
|
36966
|
+
currentNodeId = e.nodeId;
|
|
36967
|
+
});
|
|
36968
|
+
const toolCallNameMap = new Map();
|
|
36969
|
+
const onDirectEvent = (event) => {
|
|
36970
|
+
if (event.kind === EventKind.TOOL_CALL_START) {
|
|
36971
|
+
const toolName = event.data["tool_name"] ?? "";
|
|
36972
|
+
const callId = event.data["call_id"] ?? "";
|
|
36973
|
+
toolCallNameMap.set(callId, toolName);
|
|
36974
|
+
eventBus.emit("agent:tool-call", {
|
|
36975
|
+
runId,
|
|
36976
|
+
nodeId: currentNodeId,
|
|
36977
|
+
toolName,
|
|
36978
|
+
direction: "call"
|
|
36979
|
+
});
|
|
36980
|
+
} else if (event.kind === EventKind.TOOL_CALL_END) {
|
|
36981
|
+
const callId = event.data["call_id"] ?? "";
|
|
36982
|
+
const toolName = toolCallNameMap.get(callId) ?? "";
|
|
36983
|
+
toolCallNameMap.delete(callId);
|
|
36984
|
+
eventBus.emit("agent:tool-call", {
|
|
36985
|
+
runId,
|
|
36986
|
+
nodeId: currentNodeId,
|
|
36987
|
+
toolName,
|
|
36988
|
+
direction: "result"
|
|
36989
|
+
});
|
|
36990
|
+
} else if (event.kind === EventKind.LOOP_DETECTION) eventBus.emit("agent:loop-detected", {
|
|
36991
|
+
runId,
|
|
36992
|
+
nodeId: currentNodeId,
|
|
36993
|
+
windowSize: event.data["windowSize"] ?? 0,
|
|
36994
|
+
pattern: event.data["pattern"] ?? []
|
|
36995
|
+
});
|
|
36996
|
+
else if (event.kind === EventKind.STEERING_INJECTED) eventBus.emit("agent:steering-injected", {
|
|
36997
|
+
runId,
|
|
36998
|
+
nodeId: currentNodeId,
|
|
36999
|
+
message: event.data["content"] ?? ""
|
|
37000
|
+
});
|
|
37001
|
+
};
|
|
37002
|
+
try {
|
|
37003
|
+
const directBackendCfg = factoryConfig.factory?.direct_backend;
|
|
37004
|
+
directBackend = bootstrapDirectBackend({
|
|
37005
|
+
provider: directBackendCfg?.provider ?? "anthropic",
|
|
37006
|
+
model: directBackendCfg?.model ?? "claude-3-5-sonnet-20241022",
|
|
37007
|
+
maxTurns: directBackendCfg?.max_turns ?? 20,
|
|
37008
|
+
projectDir,
|
|
37009
|
+
onEvent: onDirectEvent
|
|
37010
|
+
});
|
|
37011
|
+
} catch (err) {
|
|
37012
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37013
|
+
process.stderr.write(`Error: ${msg}\n`);
|
|
37014
|
+
process.exit(1);
|
|
37015
|
+
return;
|
|
37016
|
+
}
|
|
37017
|
+
}
|
|
34720
37018
|
const adapter = options?.createAdapter ? options.createAdapter(projectDir) : createDatabaseAdapter$1({
|
|
34721
37019
|
backend: "auto",
|
|
34722
37020
|
basePath: projectDir
|
|
@@ -34726,7 +37024,7 @@ function registerFactoryCommand(program, options) {
|
|
|
34726
37024
|
const executorConfig = {
|
|
34727
37025
|
runId,
|
|
34728
37026
|
logsRoot,
|
|
34729
|
-
handlerRegistry: createDefaultRegistry(),
|
|
37027
|
+
handlerRegistry: createDefaultRegistry(directBackend ? { directBackend } : void 0),
|
|
34730
37028
|
eventBus,
|
|
34731
37029
|
dotSource,
|
|
34732
37030
|
adapter,
|
|
@@ -36454,4 +38752,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
36454
38752
|
|
|
36455
38753
|
//#endregion
|
|
36456
38754
|
export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
36457
|
-
//# sourceMappingURL=run-
|
|
38755
|
+
//# sourceMappingURL=run-Bku8fmY3.js.map
|