@fragno-dev/pi-fragment 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -3
- package/dist/browser/client/react.d.ts +44 -36
- package/dist/browser/client/react.d.ts.map +1 -1
- package/dist/browser/client/react.js +105 -22
- package/dist/browser/client/react.js.map +1 -1
- package/dist/browser/client/solid.d.ts +42 -36
- package/dist/browser/client/solid.d.ts.map +1 -1
- package/dist/browser/client/solid.js +27 -13
- package/dist/browser/client/solid.js.map +1 -1
- package/dist/browser/client/svelte.d.ts +42 -36
- package/dist/browser/client/svelte.d.ts.map +1 -1
- package/dist/browser/client/svelte.js +14 -6
- package/dist/browser/client/svelte.js.map +1 -1
- package/dist/browser/client/vanilla.d.ts +99 -39
- package/dist/browser/client/vanilla.d.ts.map +1 -1
- package/dist/browser/client/vanilla.js +151 -3
- package/dist/browser/client/vanilla.js.map +1 -1
- package/dist/browser/client/vue.d.ts +54 -38
- package/dist/browser/client/vue.d.ts.map +1 -1
- package/dist/browser/client/vue.js +25 -17
- package/dist/browser/client/vue.js.map +1 -1
- package/dist/browser/{factory-DKoO_lRA.js → clients-BscY_HVe.js} +1051 -799
- package/dist/browser/clients-BscY_HVe.js.map +1 -0
- package/dist/browser/index.d.ts +3 -776
- package/dist/browser/index.js +801 -2
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/routes-CpL_YGWK.d.ts +1560 -0
- package/dist/browser/routes-CpL_YGWK.d.ts.map +1 -0
- package/dist/cli/mod.d.ts.map +1 -1
- package/dist/cli/mod.js +245 -7
- package/dist/cli/mod.js.map +1 -1
- package/dist/node/{pi → client}/clients.d.ts +46 -36
- package/dist/node/client/clients.d.ts.map +1 -0
- package/dist/node/client/clients.js +54 -0
- package/dist/node/client/clients.js.map +1 -0
- package/dist/node/client/session-controller.d.ts +31 -0
- package/dist/node/client/session-controller.d.ts.map +1 -0
- package/dist/node/client/session-controller.js +33 -0
- package/dist/node/client/session-controller.js.map +1 -0
- package/dist/node/client/session-store.d.ts +71 -0
- package/dist/node/client/session-store.d.ts.map +1 -0
- package/dist/node/client/session-store.js +637 -0
- package/dist/node/client/session-store.js.map +1 -0
- package/dist/node/debug-log.d.ts +9 -0
- package/dist/node/debug-log.d.ts.map +1 -0
- package/dist/node/debug-log.js +58 -0
- package/dist/node/debug-log.js.map +1 -0
- package/dist/node/index.d.ts +5 -4
- package/dist/node/index.js +5 -3
- package/dist/node/pi/definition.d.ts +1 -1
- package/dist/node/pi/definition.d.ts.map +1 -1
- package/dist/node/pi/dsl.d.ts +5 -2
- package/dist/node/pi/dsl.d.ts.map +1 -1
- package/dist/node/pi/dsl.js +22 -3
- package/dist/node/pi/dsl.js.map +1 -1
- package/dist/node/pi/factory.d.ts +37 -34
- package/dist/node/pi/factory.d.ts.map +1 -1
- package/dist/node/pi/factory.js.map +1 -1
- package/dist/node/pi/mappers.js +0 -1
- package/dist/node/pi/mappers.js.map +1 -1
- package/dist/node/pi/route-schemas.js +42 -10
- package/dist/node/pi/route-schemas.js.map +1 -1
- package/dist/node/pi/types.d.ts +155 -7
- package/dist/node/pi/types.d.ts.map +1 -1
- package/dist/node/pi/types.js +6 -0
- package/dist/node/pi/types.js.map +1 -0
- package/dist/node/pi/workflow/active-session.d.ts +2 -0
- package/dist/node/pi/workflow/active-session.js +107 -0
- package/dist/node/pi/workflow/active-session.js.map +1 -0
- package/dist/node/pi/workflow/agent-runner.d.ts +13 -0
- package/dist/node/pi/workflow/agent-runner.d.ts.map +1 -0
- package/dist/node/pi/workflow/agent-runner.js +228 -0
- package/dist/node/pi/workflow/agent-runner.js.map +1 -0
- package/dist/node/pi/workflow/tool-journal.js +157 -0
- package/dist/node/pi/workflow/tool-journal.js.map +1 -0
- package/dist/node/pi/workflow/workflow.d.ts +29 -0
- package/dist/node/pi/workflow/workflow.d.ts.map +1 -0
- package/dist/node/pi/workflow/workflow.js +219 -0
- package/dist/node/pi/workflow/workflow.js.map +1 -0
- package/dist/node/routes.d.ts +38 -35
- package/dist/node/routes.d.ts.map +1 -1
- package/dist/node/routes.js +203 -132
- package/dist/node/routes.js.map +1 -1
- package/dist/node/schema.js +1 -1
- package/dist/node/schema.js.map +1 -1
- package/package.json +30 -29
- package/dist/browser/client-Bk-J98pf.d.ts +0 -679
- package/dist/browser/client-Bk-J98pf.d.ts.map +0 -1
- package/dist/browser/factory-DKoO_lRA.js.map +0 -1
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/node/pi/clients.d.ts.map +0 -1
- package/dist/node/pi/clients.js +0 -18
- package/dist/node/pi/clients.js.map +0 -1
- package/dist/node/pi/workflow.d.ts +0 -31
- package/dist/node/pi/workflow.d.ts.map +0 -1
- package/dist/node/pi/workflow.js +0 -242
- package/dist/node/pi/workflow.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { PiLogger } from "../../debug-log.js";
|
|
2
|
+
import { buildStableToolCallKey, buildToolErrorResult, clonePersistedToolCall, createPersistedToolCall, extractToolErrorMessage, takeNextReplaySequence } from "./tool-journal.js";
|
|
3
|
+
import { NonRetryableError } from "@fragno-dev/workflows";
|
|
4
|
+
import { Agent } from "@mariozechner/pi-agent-core";
|
|
5
|
+
|
|
6
|
+
//#region src/pi/workflow/agent-runner.ts
|
|
7
|
+
const isAssistantLikeMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && value.role === "assistant" && Array.isArray(value.content) && typeof value.stopReason === "string";
|
|
8
|
+
const buildStreamErrorAssistantMessage = (model, error) => ({
|
|
9
|
+
role: "assistant",
|
|
10
|
+
content: [{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: ""
|
|
13
|
+
}],
|
|
14
|
+
api: model.api,
|
|
15
|
+
provider: model.provider,
|
|
16
|
+
model: model.id,
|
|
17
|
+
usage: {
|
|
18
|
+
input: 0,
|
|
19
|
+
output: 0,
|
|
20
|
+
cacheRead: 0,
|
|
21
|
+
cacheWrite: 0,
|
|
22
|
+
totalTokens: 0,
|
|
23
|
+
cost: {
|
|
24
|
+
input: 0,
|
|
25
|
+
output: 0,
|
|
26
|
+
cacheRead: 0,
|
|
27
|
+
cacheWrite: 0,
|
|
28
|
+
total: 0
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
stopReason: "error",
|
|
32
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
33
|
+
timestamp: Date.now()
|
|
34
|
+
});
|
|
35
|
+
const wrapStreamFn = (streamFn) => streamFn ? async (...args) => {
|
|
36
|
+
const [model] = args;
|
|
37
|
+
const stream = await streamFn(...args);
|
|
38
|
+
if (typeof stream !== "object" || stream === null || Array.isArray(stream)) return stream;
|
|
39
|
+
const response = stream;
|
|
40
|
+
if (typeof response.result !== "function") return stream;
|
|
41
|
+
const originalResult = response.result.bind(stream);
|
|
42
|
+
let streamResultError;
|
|
43
|
+
response.result = async () => {
|
|
44
|
+
if (streamResultError) throw streamResultError;
|
|
45
|
+
try {
|
|
46
|
+
const result = await originalResult();
|
|
47
|
+
if (isAssistantLikeMessage(result)) return result;
|
|
48
|
+
streamResultError = /* @__PURE__ */ new Error("Stream result is not a valid assistant message.");
|
|
49
|
+
} catch (error) {
|
|
50
|
+
streamResultError = error;
|
|
51
|
+
}
|
|
52
|
+
const errorMessage = buildStreamErrorAssistantMessage(model, streamResultError);
|
|
53
|
+
if ("push" in stream && typeof stream.push === "function") stream.push({
|
|
54
|
+
type: "error",
|
|
55
|
+
reason: "error",
|
|
56
|
+
error: errorMessage
|
|
57
|
+
});
|
|
58
|
+
return errorMessage;
|
|
59
|
+
};
|
|
60
|
+
return stream;
|
|
61
|
+
} : void 0;
|
|
62
|
+
const resolveTool = async (name, factory, context) => {
|
|
63
|
+
if (!factory) throw new NonRetryableError(`Tool ${name} not found.`);
|
|
64
|
+
if (typeof factory === "function") {
|
|
65
|
+
const tool = await factory(context);
|
|
66
|
+
if (!tool) throw new NonRetryableError(`Tool ${name} returned no definition.`);
|
|
67
|
+
return tool;
|
|
68
|
+
}
|
|
69
|
+
return factory;
|
|
70
|
+
};
|
|
71
|
+
const wrapToolWithReplay = (options) => ({
|
|
72
|
+
...options.tool,
|
|
73
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
74
|
+
const sessionId = options.context.session.id;
|
|
75
|
+
const toolCallIdValue = String(toolCallId);
|
|
76
|
+
const key = buildStableToolCallKey(sessionId, options.context.turnId, toolCallIdValue);
|
|
77
|
+
const replayEntry = options.context.replay.cache.get(key);
|
|
78
|
+
if (replayEntry) {
|
|
79
|
+
options.context.replay.journal.push(clonePersistedToolCall({
|
|
80
|
+
...replayEntry,
|
|
81
|
+
source: "replay",
|
|
82
|
+
seq: takeNextReplaySequence(options.context.replay)
|
|
83
|
+
}));
|
|
84
|
+
PiLogger.debug("tool replay hit", {
|
|
85
|
+
sessionId,
|
|
86
|
+
turnId: options.context.turnId,
|
|
87
|
+
toolName: replayEntry.toolName,
|
|
88
|
+
key
|
|
89
|
+
});
|
|
90
|
+
if (replayEntry.isError) throw new Error(extractToolErrorMessage(replayEntry.result));
|
|
91
|
+
return structuredClone(replayEntry.result);
|
|
92
|
+
}
|
|
93
|
+
const argsSnapshot = structuredClone(params);
|
|
94
|
+
const recordResult = (result, isError) => {
|
|
95
|
+
const entry = createPersistedToolCall({
|
|
96
|
+
sessionId,
|
|
97
|
+
turnId: options.context.turnId,
|
|
98
|
+
toolCallId: toolCallIdValue,
|
|
99
|
+
toolName: options.toolName,
|
|
100
|
+
args: argsSnapshot,
|
|
101
|
+
result,
|
|
102
|
+
isError,
|
|
103
|
+
source: "executed",
|
|
104
|
+
seq: takeNextReplaySequence(options.context.replay)
|
|
105
|
+
});
|
|
106
|
+
options.context.replay.cache.set(entry.key, clonePersistedToolCall(entry));
|
|
107
|
+
options.context.replay.journal.push(clonePersistedToolCall(entry));
|
|
108
|
+
return entry;
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const result = await options.tool.execute(toolCallId, params, signal, onUpdate);
|
|
112
|
+
recordResult(structuredClone(result), false);
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
recordResult(buildToolErrorResult(error), true);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const resolveTools = async (options) => {
|
|
121
|
+
const toolNames = options.agent.tools ?? [];
|
|
122
|
+
if (toolNames.length === 0) return [];
|
|
123
|
+
const context = {
|
|
124
|
+
session: options.session,
|
|
125
|
+
turnId: options.turnId,
|
|
126
|
+
toolConfig: options.agent.toolConfig ?? null,
|
|
127
|
+
messages: options.messages,
|
|
128
|
+
replay: options.replay
|
|
129
|
+
};
|
|
130
|
+
const resolved = [];
|
|
131
|
+
for (const name of toolNames) {
|
|
132
|
+
const tool = await resolveTool(name, options.tools[name], context);
|
|
133
|
+
resolved.push(wrapToolWithReplay({
|
|
134
|
+
toolName: name,
|
|
135
|
+
tool,
|
|
136
|
+
context
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
return resolved;
|
|
140
|
+
};
|
|
141
|
+
const findLastAssistantMessage = (messages) => messages.findLast((m) => m.role === "assistant") ?? null;
|
|
142
|
+
const createAgent = async (options) => {
|
|
143
|
+
const now = /* @__PURE__ */ new Date();
|
|
144
|
+
const session = {
|
|
145
|
+
id: options.params.sessionId,
|
|
146
|
+
name: null,
|
|
147
|
+
status: "active",
|
|
148
|
+
agent: options.params.agentName,
|
|
149
|
+
steeringMode: options.steeringMode,
|
|
150
|
+
metadata: null,
|
|
151
|
+
tags: [],
|
|
152
|
+
createdAt: now,
|
|
153
|
+
updatedAt: now
|
|
154
|
+
};
|
|
155
|
+
const agentTools = await resolveTools({
|
|
156
|
+
agent: options.agent,
|
|
157
|
+
tools: options.tools,
|
|
158
|
+
session,
|
|
159
|
+
turnId: options.turnId,
|
|
160
|
+
messages: options.messages,
|
|
161
|
+
replay: options.replay
|
|
162
|
+
});
|
|
163
|
+
const initialState = {
|
|
164
|
+
systemPrompt: options.params.systemPrompt ?? options.agent.systemPrompt,
|
|
165
|
+
model: options.agent.model,
|
|
166
|
+
tools: agentTools,
|
|
167
|
+
messages: options.messages
|
|
168
|
+
};
|
|
169
|
+
if (options.agent.thinkingLevel) initialState.thinkingLevel = options.agent.thinkingLevel;
|
|
170
|
+
const agent = new Agent({
|
|
171
|
+
initialState,
|
|
172
|
+
streamFn: wrapStreamFn(options.agent.streamFn),
|
|
173
|
+
convertToLlm: options.agent.convertToLlm,
|
|
174
|
+
transformContext: options.agent.transformContext,
|
|
175
|
+
getApiKey: options.agent.getApiKey,
|
|
176
|
+
thinkingBudgets: options.agent.thinkingBudgets,
|
|
177
|
+
maxRetryDelayMs: options.agent.maxRetryDelayMs,
|
|
178
|
+
sessionId: options.params.sessionId
|
|
179
|
+
});
|
|
180
|
+
agent.setSteeringMode(options.steeringMode);
|
|
181
|
+
const trace = [];
|
|
182
|
+
const unsubscribe = agent.subscribe((event) => {
|
|
183
|
+
trace.push(event);
|
|
184
|
+
options.onEvent?.(event);
|
|
185
|
+
if (!options.agent.onEvent) return;
|
|
186
|
+
try {
|
|
187
|
+
options.agent.onEvent(event, {
|
|
188
|
+
sessionId: options.params.sessionId,
|
|
189
|
+
turnId: options.turnId
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn("Agent onEvent hook failed.", {
|
|
193
|
+
error,
|
|
194
|
+
sessionId: options.params.sessionId,
|
|
195
|
+
turnId: options.turnId,
|
|
196
|
+
agent: options.agent.name
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
try {
|
|
201
|
+
await agent.continue();
|
|
202
|
+
} finally {
|
|
203
|
+
unsubscribe();
|
|
204
|
+
}
|
|
205
|
+
const assistant = findLastAssistantMessage(agent.state.messages);
|
|
206
|
+
const stateError = agent.state.error;
|
|
207
|
+
if (stateError) throw stateError instanceof Error ? stateError : new Error(String(stateError));
|
|
208
|
+
if (assistant && "errorMessage" in assistant && typeof assistant.errorMessage === "string") throw new Error(assistant.errorMessage);
|
|
209
|
+
return {
|
|
210
|
+
agent,
|
|
211
|
+
trace,
|
|
212
|
+
assistant,
|
|
213
|
+
toolJournal: options.replay.journal.map(clonePersistedToolCall)
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
const runAgentTurn = async (options) => {
|
|
217
|
+
const result = await createAgent(options);
|
|
218
|
+
return {
|
|
219
|
+
messages: result.agent.state.messages,
|
|
220
|
+
trace: result.trace,
|
|
221
|
+
assistant: result.assistant,
|
|
222
|
+
toolJournal: result.toolJournal
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
export { runAgentTurn };
|
|
228
|
+
//# sourceMappingURL=agent-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runner.js","names":[],"sources":["../../../../src/pi/workflow/agent-runner.ts"],"sourcesContent":["import { NonRetryableError } from \"@fragno-dev/workflows\";\n\nimport {\n Agent,\n type AgentEvent,\n type AgentMessage,\n type AgentState,\n type AgentTool,\n} from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage } from \"@mariozechner/pi-ai\";\n\nimport { PiLogger } from \"../../debug-log\";\nimport type {\n PiAgentDefinition,\n PiPersistedToolCall,\n PiPersistedToolResult,\n PiSession,\n PiToolFactory,\n PiToolFactoryContext,\n PiToolReplayContext,\n PiToolRegistry,\n} from \"../types\";\nimport {\n buildStableToolCallKey,\n buildToolErrorResult,\n clonePersistedToolCall,\n createPersistedToolCall,\n extractToolErrorMessage,\n takeNextReplaySequence,\n} from \"./tool-journal\";\n\nexport type AgentLoopParams = {\n sessionId: string;\n agentName: string;\n systemPrompt?: string;\n initialMessages?: AgentMessage[];\n};\n\n// --- Stream wrapping ---\n\ntype AgentStreamFn = NonNullable<PiAgentDefinition[\"streamFn\"]>;\ntype AgentStreamFnArgs = Parameters<AgentStreamFn>;\n\nconst isAssistantLikeMessage = (value: unknown): value is AssistantMessage =>\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (value as { role?: unknown }).role === \"assistant\" &&\n Array.isArray((value as { content?: unknown }).content) &&\n typeof (value as { stopReason?: unknown }).stopReason === \"string\";\n\nconst buildStreamErrorAssistantMessage = (\n model: AgentStreamFnArgs[0],\n error: unknown,\n): AssistantMessage => ({\n role: \"assistant\",\n content: [{ type: \"text\", text: \"\" }],\n api: model.api,\n provider: model.provider,\n model: model.id,\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: \"error\",\n errorMessage: error instanceof Error ? error.message : String(error),\n timestamp: Date.now(),\n});\n\nconst wrapStreamFn = (streamFn: PiAgentDefinition[\"streamFn\"]) =>\n streamFn\n ? async (...args: AgentStreamFnArgs) => {\n const [model] = args;\n const stream = await streamFn(...args);\n if (typeof stream !== \"object\" || stream === null || Array.isArray(stream)) {\n return stream;\n }\n\n const response = stream as { result?: unknown };\n if (typeof response.result !== \"function\") {\n return stream;\n }\n\n const originalResult = response.result.bind(stream) as () => Promise<unknown>;\n let streamResultError: unknown | undefined;\n response.result = async () => {\n if (streamResultError) {\n throw streamResultError;\n }\n\n try {\n const result = await originalResult();\n if (isAssistantLikeMessage(result)) {\n return result;\n }\n\n streamResultError = new Error(\"Stream result is not a valid assistant message.\");\n } catch (error) {\n streamResultError = error;\n }\n\n const errorMessage = buildStreamErrorAssistantMessage(model, streamResultError);\n if (\"push\" in stream && typeof stream.push === \"function\") {\n stream.push({ type: \"error\", reason: \"error\", error: errorMessage });\n }\n return errorMessage;\n };\n\n return stream;\n }\n : undefined;\n\n// --- Tool resolution ---\n\nconst resolveTool = async (\n name: string,\n factory: PiToolFactory | undefined,\n context: PiToolFactoryContext,\n): Promise<AgentTool> => {\n if (!factory) {\n throw new NonRetryableError(`Tool ${name} not found.`);\n }\n if (typeof factory === \"function\") {\n const tool = await factory(context);\n if (!tool) {\n throw new NonRetryableError(`Tool ${name} returned no definition.`);\n }\n return tool;\n }\n return factory;\n};\n\nconst wrapToolWithReplay = (options: {\n toolName: string;\n tool: AgentTool;\n context: PiToolFactoryContext;\n}): AgentTool => ({\n ...options.tool,\n execute: async (toolCallId, params, signal, onUpdate) => {\n const sessionId = options.context.session.id;\n const toolCallIdValue = String(toolCallId);\n const key = buildStableToolCallKey(sessionId, options.context.turnId, toolCallIdValue);\n const replayEntry = options.context.replay.cache.get(key);\n\n if (replayEntry) {\n options.context.replay.journal.push(\n clonePersistedToolCall({\n ...replayEntry,\n source: \"replay\",\n seq: takeNextReplaySequence(options.context.replay),\n }),\n );\n PiLogger.debug(\"tool replay hit\", {\n sessionId,\n turnId: options.context.turnId,\n toolName: replayEntry.toolName,\n key,\n });\n if (replayEntry.isError) {\n throw new Error(extractToolErrorMessage(replayEntry.result));\n }\n return structuredClone(replayEntry.result);\n }\n\n const argsSnapshot = structuredClone(params) as Record<string, unknown>;\n const recordResult = (result: PiPersistedToolResult, isError: boolean) => {\n const entry = createPersistedToolCall({\n sessionId,\n turnId: options.context.turnId,\n toolCallId: toolCallIdValue,\n toolName: options.toolName,\n args: argsSnapshot,\n result,\n isError,\n source: \"executed\",\n seq: takeNextReplaySequence(options.context.replay),\n });\n options.context.replay.cache.set(entry.key, clonePersistedToolCall(entry));\n options.context.replay.journal.push(clonePersistedToolCall(entry));\n return entry;\n };\n\n try {\n const result = await options.tool.execute(toolCallId, params, signal, onUpdate);\n recordResult(structuredClone(result) as PiPersistedToolResult, false);\n return result;\n } catch (error) {\n recordResult(buildToolErrorResult(error), true);\n throw error;\n }\n },\n});\n\nconst resolveTools = async (options: {\n agent: PiAgentDefinition;\n tools: PiToolRegistry;\n session: PiSession;\n turnId: string;\n messages: AgentMessage[];\n replay: PiToolReplayContext;\n}): Promise<AgentTool[]> => {\n const toolNames = options.agent.tools ?? [];\n if (toolNames.length === 0) {\n return [];\n }\n\n const context: PiToolFactoryContext = {\n session: options.session,\n turnId: options.turnId,\n toolConfig: options.agent.toolConfig ?? null,\n messages: options.messages,\n replay: options.replay,\n };\n\n const resolved: AgentTool[] = [];\n for (const name of toolNames) {\n const tool = await resolveTool(name, options.tools[name], context);\n resolved.push(wrapToolWithReplay({ toolName: name, tool, context }));\n }\n\n return resolved;\n};\n\n// --- Agent lifecycle ---\n\nconst findLastAssistantMessage = (messages: AgentMessage[]): AgentMessage | null =>\n messages.findLast((m) => m.role === \"assistant\") ?? null;\n\nconst createAgent = async (options: {\n agent: PiAgentDefinition;\n tools: PiToolRegistry;\n params: AgentLoopParams;\n messages: AgentMessage[];\n steeringMode: \"all\" | \"one-at-a-time\";\n turnId: string;\n replay: PiToolReplayContext;\n onEvent?: (event: AgentEvent) => void;\n}): Promise<{\n agent: Agent;\n trace: AgentEvent[];\n assistant: AgentMessage | null;\n toolJournal: PiPersistedToolCall[];\n}> => {\n const now = new Date();\n const session: PiSession = {\n id: options.params.sessionId,\n name: null,\n status: \"active\",\n agent: options.params.agentName,\n steeringMode: options.steeringMode,\n metadata: null,\n tags: [],\n createdAt: now,\n updatedAt: now,\n };\n\n const agentTools = await resolveTools({\n agent: options.agent,\n tools: options.tools,\n session,\n turnId: options.turnId,\n messages: options.messages,\n replay: options.replay,\n });\n\n const initialState: Partial<AgentState> = {\n systemPrompt: options.params.systemPrompt ?? options.agent.systemPrompt,\n model: options.agent.model,\n tools: agentTools,\n messages: options.messages,\n };\n\n if (options.agent.thinkingLevel) {\n initialState.thinkingLevel = options.agent.thinkingLevel;\n }\n\n const agent = new Agent({\n initialState,\n streamFn: wrapStreamFn(options.agent.streamFn),\n convertToLlm: options.agent.convertToLlm,\n transformContext: options.agent.transformContext,\n getApiKey: options.agent.getApiKey,\n thinkingBudgets: options.agent.thinkingBudgets,\n maxRetryDelayMs: options.agent.maxRetryDelayMs,\n sessionId: options.params.sessionId,\n });\n\n agent.setSteeringMode(options.steeringMode);\n\n const trace: AgentEvent[] = [];\n const unsubscribe = agent.subscribe((event) => {\n trace.push(event);\n options.onEvent?.(event);\n if (!options.agent.onEvent) {\n return;\n }\n try {\n options.agent.onEvent(event, { sessionId: options.params.sessionId, turnId: options.turnId });\n } catch (error) {\n console.warn(\"Agent onEvent hook failed.\", {\n error,\n sessionId: options.params.sessionId,\n turnId: options.turnId,\n agent: options.agent.name,\n });\n }\n });\n\n try {\n await agent.continue();\n } finally {\n unsubscribe();\n }\n\n const assistant = findLastAssistantMessage(agent.state.messages);\n const stateError = agent.state.error as unknown;\n if (stateError) {\n throw stateError instanceof Error ? stateError : new Error(String(stateError));\n }\n if (assistant && \"errorMessage\" in assistant && typeof assistant.errorMessage === \"string\") {\n throw new Error(assistant.errorMessage);\n }\n return {\n agent,\n trace,\n assistant,\n toolJournal: options.replay.journal.map(clonePersistedToolCall),\n };\n};\n\nexport const runAgentTurn = async (options: {\n params: AgentLoopParams;\n agent: PiAgentDefinition;\n tools: PiToolRegistry;\n messages: AgentMessage[];\n steeringMode: \"all\" | \"one-at-a-time\";\n turnId: string;\n replay: PiToolReplayContext;\n onEvent?: (event: AgentEvent) => void;\n}) => {\n const result = await createAgent(options);\n\n return {\n messages: result.agent.state.messages,\n trace: result.trace,\n assistant: result.assistant,\n toolJournal: result.toolJournal,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { PI_TOOL_JOURNAL_VERSION } from "../types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { NonRetryableError } from "@fragno-dev/workflows";
|
|
4
|
+
|
|
5
|
+
//#region src/pi/workflow/tool-journal.ts
|
|
6
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
7
|
+
const TOOL_JOURNAL_FIELD = "toolJournal";
|
|
8
|
+
const persistedToolResultContentSchema = z.union([z.object({
|
|
9
|
+
type: z.literal("text"),
|
|
10
|
+
text: z.string(),
|
|
11
|
+
textSignature: z.string().optional()
|
|
12
|
+
}), z.object({
|
|
13
|
+
type: z.literal("image"),
|
|
14
|
+
data: z.string(),
|
|
15
|
+
mimeType: z.string()
|
|
16
|
+
})]);
|
|
17
|
+
const persistedToolResultSchema = z.object({
|
|
18
|
+
content: z.array(persistedToolResultContentSchema),
|
|
19
|
+
details: z.unknown()
|
|
20
|
+
});
|
|
21
|
+
const persistedToolCallSchema = z.object({
|
|
22
|
+
version: z.literal(PI_TOOL_JOURNAL_VERSION),
|
|
23
|
+
key: z.string(),
|
|
24
|
+
sessionId: z.string(),
|
|
25
|
+
turnId: z.string(),
|
|
26
|
+
toolCallId: z.string(),
|
|
27
|
+
toolName: z.string(),
|
|
28
|
+
args: z.record(z.string(), z.unknown()),
|
|
29
|
+
result: persistedToolResultSchema,
|
|
30
|
+
isError: z.boolean(),
|
|
31
|
+
source: z.enum(["executed", "replay"]),
|
|
32
|
+
capturedAt: z.number().finite(),
|
|
33
|
+
seq: z.number().int().nonnegative()
|
|
34
|
+
});
|
|
35
|
+
const formatToolJournalIssueLocation = (location, issue) => {
|
|
36
|
+
if (issue.path.length === 0) return location;
|
|
37
|
+
return `${location}.${issue.path.join(".")}`;
|
|
38
|
+
};
|
|
39
|
+
const buildStableToolCallKey = (sessionId, turnId, toolCallId) => `${sessionId}:${turnId}:${toolCallId}`;
|
|
40
|
+
const compareToolJournalEntries = (a, b) => a.seq !== b.seq ? a.seq - b.seq : a.capturedAt - b.capturedAt;
|
|
41
|
+
const clonePersistedToolCall = (entry) => structuredClone(entry);
|
|
42
|
+
const buildToolErrorResult = (error) => ({
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: error instanceof Error ? error.message : String(error)
|
|
46
|
+
}],
|
|
47
|
+
details: {}
|
|
48
|
+
});
|
|
49
|
+
const extractToolErrorMessage = (result) => {
|
|
50
|
+
for (const block of result.content) if (block.type === "text") return block.text;
|
|
51
|
+
return "Tool execution failed";
|
|
52
|
+
};
|
|
53
|
+
const createPersistedToolCall = (options) => ({
|
|
54
|
+
version: PI_TOOL_JOURNAL_VERSION,
|
|
55
|
+
key: buildStableToolCallKey(options.sessionId, options.turnId, options.toolCallId),
|
|
56
|
+
sessionId: options.sessionId,
|
|
57
|
+
turnId: options.turnId,
|
|
58
|
+
toolCallId: options.toolCallId,
|
|
59
|
+
toolName: options.toolName,
|
|
60
|
+
args: options.args,
|
|
61
|
+
result: options.result,
|
|
62
|
+
isError: options.isError,
|
|
63
|
+
source: options.source,
|
|
64
|
+
capturedAt: Date.now(),
|
|
65
|
+
seq: options.seq
|
|
66
|
+
});
|
|
67
|
+
const parsePersistedToolCall = (value, location) => {
|
|
68
|
+
if (!isRecord(value) || Array.isArray(value)) throw new NonRetryableError(`Invalid tool journal entry at ${location}.`);
|
|
69
|
+
if (value["version"] !== PI_TOOL_JOURNAL_VERSION) throw new NonRetryableError(`Unsupported tool journal version at ${location}: ${String(value["version"])}`);
|
|
70
|
+
const parsed = persistedToolCallSchema.safeParse(value);
|
|
71
|
+
if (!parsed.success) {
|
|
72
|
+
const issue = parsed.error.issues[0];
|
|
73
|
+
throw new NonRetryableError(`Invalid tool journal entry at ${formatToolJournalIssueLocation(location, issue)}: ${issue.message}`);
|
|
74
|
+
}
|
|
75
|
+
return parsed.data;
|
|
76
|
+
};
|
|
77
|
+
const parsePersistedToolJournal = (assistantResult, stepName) => {
|
|
78
|
+
if (!isRecord(assistantResult) || Array.isArray(assistantResult)) throw new NonRetryableError(`Assistant step ${stepName} returned an invalid result object.`);
|
|
79
|
+
if (!(TOOL_JOURNAL_FIELD in assistantResult)) return [];
|
|
80
|
+
const raw = assistantResult[TOOL_JOURNAL_FIELD];
|
|
81
|
+
if (!Array.isArray(raw)) throw new NonRetryableError(`Assistant step ${stepName} contains an invalid tool journal.`);
|
|
82
|
+
return raw.map((entry, index) => parsePersistedToolCall(entry, `${stepName}[${index}]`));
|
|
83
|
+
};
|
|
84
|
+
const hydrateReplayCache = (cache, entries) => {
|
|
85
|
+
const sorted = [...entries].sort(compareToolJournalEntries);
|
|
86
|
+
for (const entry of sorted) {
|
|
87
|
+
if (entry.source === "replay" && cache.has(entry.key)) continue;
|
|
88
|
+
cache.set(entry.key, clonePersistedToolCall(entry));
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const reduceBashSideEffects = (state, entry) => {
|
|
92
|
+
const details = isRecord(entry.result.details) ? entry.result.details : {};
|
|
93
|
+
const base = isRecord(state) ? state : {};
|
|
94
|
+
const next = {
|
|
95
|
+
cwd: typeof details.cwd === "string" ? details.cwd : typeof base.cwd === "string" ? base.cwd : "/",
|
|
96
|
+
files: isRecord(base.files) && !Array.isArray(base.files) ? { ...base.files } : {}
|
|
97
|
+
};
|
|
98
|
+
const detailsFiles = isRecord(details.files) && !Array.isArray(details.files) ? details.files : null;
|
|
99
|
+
if (detailsFiles) for (const [path, value] of Object.entries(detailsFiles)) next.files[path] = value;
|
|
100
|
+
if (Array.isArray(details.writes)) for (const item of details.writes) {
|
|
101
|
+
if (!isRecord(item)) continue;
|
|
102
|
+
const writeEntry = item;
|
|
103
|
+
const path = typeof writeEntry.path === "string" ? writeEntry.path : null;
|
|
104
|
+
if (!path) continue;
|
|
105
|
+
next.files[path] = typeof writeEntry.content === "string" ? writeEntry.content : "";
|
|
106
|
+
}
|
|
107
|
+
const deletes = Array.isArray(details.deletes) ? details.deletes : Array.isArray(details.deletedPaths) ? details.deletedPaths : [];
|
|
108
|
+
for (const path of deletes) if (typeof path === "string") delete next.files[path];
|
|
109
|
+
return next;
|
|
110
|
+
};
|
|
111
|
+
const buildSideEffectState = (options) => {
|
|
112
|
+
const reducers = {
|
|
113
|
+
bash: reduceBashSideEffects,
|
|
114
|
+
...options.reducers
|
|
115
|
+
};
|
|
116
|
+
const state = {};
|
|
117
|
+
const journal = [...options.cache.values()].sort(compareToolJournalEntries);
|
|
118
|
+
for (const entry of journal) {
|
|
119
|
+
const reducer = reducers[entry.toolName];
|
|
120
|
+
if (!reducer) continue;
|
|
121
|
+
try {
|
|
122
|
+
state[entry.toolName] = reducer(state[entry.toolName], entry, {
|
|
123
|
+
key: entry.key,
|
|
124
|
+
sessionId: entry.sessionId,
|
|
125
|
+
turnId: entry.turnId
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new NonRetryableError(`Tool side-effect reducer failed for ${entry.toolName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return state;
|
|
132
|
+
};
|
|
133
|
+
const replaySequenceByContext = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
const takeNextReplaySequence = (replayContext) => {
|
|
135
|
+
const current = replaySequenceByContext.get(replayContext) ?? 0;
|
|
136
|
+
replaySequenceByContext.set(replayContext, current + 1);
|
|
137
|
+
return current;
|
|
138
|
+
};
|
|
139
|
+
const createReplayContext = (options) => {
|
|
140
|
+
const localCache = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const [key, entry] of options.cache.entries()) localCache.set(key, clonePersistedToolCall(entry));
|
|
142
|
+
const replayContext = {
|
|
143
|
+
cache: localCache,
|
|
144
|
+
journal: [],
|
|
145
|
+
sideEffects: buildSideEffectState({
|
|
146
|
+
cache: localCache,
|
|
147
|
+
reducers: options.reducers
|
|
148
|
+
})
|
|
149
|
+
};
|
|
150
|
+
const maxSeq = [...localCache.values()].reduce((max, entry) => entry.seq > max ? entry.seq : max, -1);
|
|
151
|
+
replaySequenceByContext.set(replayContext, maxSeq + 1);
|
|
152
|
+
return replayContext;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { buildStableToolCallKey, buildToolErrorResult, clonePersistedToolCall, createPersistedToolCall, createReplayContext, extractToolErrorMessage, hydrateReplayCache, parsePersistedToolJournal, takeNextReplaySequence };
|
|
157
|
+
//# sourceMappingURL=tool-journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-journal.js","names":[],"sources":["../../../../src/pi/workflow/tool-journal.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { NonRetryableError } from \"@fragno-dev/workflows\";\n\nimport {\n PI_TOOL_JOURNAL_VERSION,\n type PiPersistedToolCall,\n type PiPersistedToolResult,\n type PiToolReplayContext,\n type PiToolSideEffectReducerRegistry,\n} from \"../types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nconst TOOL_JOURNAL_FIELD = \"toolJournal\";\n\nconst persistedToolResultContentSchema = z.union([\n z.object({\n type: z.literal(\"text\"),\n text: z.string(),\n textSignature: z.string().optional(),\n }),\n z.object({\n type: z.literal(\"image\"),\n data: z.string(),\n mimeType: z.string(),\n }),\n]);\n\nconst persistedToolResultSchema = z.object({\n content: z.array(persistedToolResultContentSchema),\n details: z.unknown(),\n});\n\nconst persistedToolCallSchema = z.object({\n version: z.literal(PI_TOOL_JOURNAL_VERSION),\n key: z.string(),\n sessionId: z.string(),\n turnId: z.string(),\n toolCallId: z.string(),\n toolName: z.string(),\n args: z.record(z.string(), z.unknown()),\n result: persistedToolResultSchema,\n isError: z.boolean(),\n source: z.enum([\"executed\", \"replay\"]),\n capturedAt: z.number().finite(),\n seq: z.number().int().nonnegative(),\n});\n\nconst formatToolJournalIssueLocation = (location: string, issue: z.ZodIssue): string => {\n if (issue.path.length === 0) {\n return location;\n }\n\n return `${location}.${issue.path.join(\".\")}`;\n};\n\nexport const buildStableToolCallKey = (sessionId: string, turnId: string, toolCallId: string) =>\n `${sessionId}:${turnId}:${toolCallId}`;\n\nexport const compareToolJournalEntries = (a: PiPersistedToolCall, b: PiPersistedToolCall) =>\n a.seq !== b.seq ? a.seq - b.seq : a.capturedAt - b.capturedAt;\n\nexport const clonePersistedToolCall = (entry: PiPersistedToolCall): PiPersistedToolCall =>\n structuredClone(entry);\n\nexport const buildToolErrorResult = (error: unknown): PiPersistedToolResult => ({\n content: [{ type: \"text\", text: error instanceof Error ? error.message : String(error) }],\n details: {},\n});\n\nexport const extractToolErrorMessage = (result: PiPersistedToolResult): string => {\n for (const block of result.content) {\n if (block.type === \"text\") {\n return block.text;\n }\n }\n return \"Tool execution failed\";\n};\n\nexport const createPersistedToolCall = (options: {\n sessionId: string;\n turnId: string;\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n result: PiPersistedToolResult;\n isError: boolean;\n source: PiPersistedToolCall[\"source\"];\n seq: number;\n}): PiPersistedToolCall => ({\n version: PI_TOOL_JOURNAL_VERSION,\n key: buildStableToolCallKey(options.sessionId, options.turnId, options.toolCallId),\n sessionId: options.sessionId,\n turnId: options.turnId,\n toolCallId: options.toolCallId,\n toolName: options.toolName,\n args: options.args,\n result: options.result,\n isError: options.isError,\n source: options.source,\n capturedAt: Date.now(),\n seq: options.seq,\n});\n\nconst parsePersistedToolCall = (value: unknown, location: string): PiPersistedToolCall => {\n if (!isRecord(value) || Array.isArray(value)) {\n throw new NonRetryableError(`Invalid tool journal entry at ${location}.`);\n }\n\n if (value[\"version\"] !== PI_TOOL_JOURNAL_VERSION) {\n throw new NonRetryableError(\n `Unsupported tool journal version at ${location}: ${String(value[\"version\"])}`,\n );\n }\n\n const parsed = persistedToolCallSchema.safeParse(value);\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n throw new NonRetryableError(\n `Invalid tool journal entry at ${formatToolJournalIssueLocation(location, issue)}: ${issue.message}`,\n );\n }\n\n return parsed.data as PiPersistedToolCall;\n};\n\nexport const parsePersistedToolJournal = (\n assistantResult: unknown,\n stepName: string,\n): PiPersistedToolCall[] => {\n if (!isRecord(assistantResult) || Array.isArray(assistantResult)) {\n throw new NonRetryableError(`Assistant step ${stepName} returned an invalid result object.`);\n }\n if (!(TOOL_JOURNAL_FIELD in assistantResult)) {\n return [];\n }\n const raw = assistantResult[TOOL_JOURNAL_FIELD];\n if (!Array.isArray(raw)) {\n throw new NonRetryableError(`Assistant step ${stepName} contains an invalid tool journal.`);\n }\n return raw.map((entry, index) => parsePersistedToolCall(entry, `${stepName}[${index}]`));\n};\n\nexport const hydrateReplayCache = (\n cache: PiToolReplayContext[\"cache\"],\n entries: PiPersistedToolCall[],\n): void => {\n const sorted = [...entries].sort(compareToolJournalEntries);\n for (const entry of sorted) {\n if (entry.source === \"replay\" && cache.has(entry.key)) {\n continue;\n }\n cache.set(entry.key, clonePersistedToolCall(entry));\n }\n};\n\n// --- Side-effect reducers ---\n\nconst reduceBashSideEffects = (state: unknown, entry: PiPersistedToolCall): unknown => {\n const details = isRecord(entry.result.details)\n ? (entry.result.details as {\n cwd?: unknown;\n files?: unknown;\n writes?: unknown;\n deletes?: unknown;\n deletedPaths?: unknown;\n })\n : {};\n const base = isRecord(state) ? (state as { cwd?: unknown; files?: unknown }) : {};\n\n const next = {\n cwd:\n typeof details.cwd === \"string\" ? details.cwd : typeof base.cwd === \"string\" ? base.cwd : \"/\",\n files:\n isRecord(base.files) && !Array.isArray(base.files)\n ? ({ ...(base.files as Record<string, unknown>) } as Record<string, unknown>)\n : ({} as Record<string, unknown>),\n };\n\n const detailsFiles =\n isRecord(details.files) && !Array.isArray(details.files) ? details.files : null;\n if (detailsFiles) {\n for (const [path, value] of Object.entries(detailsFiles)) {\n next.files[path] = value;\n }\n }\n\n if (Array.isArray(details.writes)) {\n for (const item of details.writes) {\n if (!isRecord(item)) {\n continue;\n }\n const writeEntry = item as { path?: unknown; content?: unknown };\n const path = typeof writeEntry.path === \"string\" ? writeEntry.path : null;\n if (!path) {\n continue;\n }\n next.files[path] = typeof writeEntry.content === \"string\" ? writeEntry.content : \"\";\n }\n }\n\n const deletes = Array.isArray(details.deletes)\n ? details.deletes\n : Array.isArray(details.deletedPaths)\n ? details.deletedPaths\n : [];\n for (const path of deletes) {\n if (typeof path === \"string\") {\n delete next.files[path];\n }\n }\n\n return next;\n};\n\nconst buildSideEffectState = (options: {\n cache: PiToolReplayContext[\"cache\"];\n reducers?: PiToolSideEffectReducerRegistry;\n}): Record<string, unknown> => {\n const reducers: PiToolSideEffectReducerRegistry = {\n bash: reduceBashSideEffects,\n ...options.reducers,\n };\n\n const state: Record<string, unknown> = {};\n const journal = [...options.cache.values()].sort(compareToolJournalEntries);\n\n for (const entry of journal) {\n const reducer = reducers[entry.toolName];\n if (!reducer) {\n continue;\n }\n\n try {\n state[entry.toolName] = reducer(state[entry.toolName], entry, {\n key: entry.key,\n sessionId: entry.sessionId,\n turnId: entry.turnId,\n });\n } catch (error) {\n throw new NonRetryableError(\n `Tool side-effect reducer failed for ${entry.toolName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return state;\n};\n\n// --- Replay context ---\n\nconst replaySequenceByContext = new WeakMap<PiToolReplayContext, number>();\n\nexport const takeNextReplaySequence = (replayContext: PiToolReplayContext): number => {\n const current = replaySequenceByContext.get(replayContext) ?? 0;\n replaySequenceByContext.set(replayContext, current + 1);\n return current;\n};\n\nexport const createReplayContext = (options: {\n cache: PiToolReplayContext[\"cache\"];\n reducers?: PiToolSideEffectReducerRegistry;\n}): PiToolReplayContext => {\n const localCache: PiToolReplayContext[\"cache\"] = new Map();\n for (const [key, entry] of options.cache.entries()) {\n localCache.set(key, clonePersistedToolCall(entry));\n }\n\n const replayContext: PiToolReplayContext = {\n cache: localCache,\n journal: [],\n sideEffects: buildSideEffectState({ cache: localCache, reducers: options.reducers }),\n };\n const maxSeq = [...localCache.values()].reduce(\n (max, entry) => (entry.seq > max ? entry.seq : max),\n -1,\n );\n replaySequenceByContext.set(replayContext, maxSeq + 1);\n return replayContext;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./active-session.js";
|
|
2
|
+
import { AgentLoopParams } from "./agent-runner.js";
|
|
3
|
+
import { PiAgentLoopState, PiAgentRegistry, PiFragmentConfig, PiToolRegistry, PiToolSideEffectReducerRegistry } from "../types.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import * as _fragno_dev_workflows0 from "@fragno-dev/workflows";
|
|
6
|
+
import { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
7
|
+
|
|
8
|
+
//#region src/pi/workflow/workflow.d.ts
|
|
9
|
+
declare const PI_WORKFLOW_NAME = "agent-loop-workflow";
|
|
10
|
+
type WorkflowsOptions = {
|
|
11
|
+
agents: PiAgentRegistry;
|
|
12
|
+
tools: PiToolRegistry;
|
|
13
|
+
toolSideEffectReducers?: PiToolSideEffectReducerRegistry;
|
|
14
|
+
logging?: PiFragmentConfig["logging"];
|
|
15
|
+
};
|
|
16
|
+
declare const createPiAgentLoopWorkflow: (options: WorkflowsOptions) => _fragno_dev_workflows0.WorkflowDefinition<AgentLoopParams, {
|
|
17
|
+
messages: AgentMessage[];
|
|
18
|
+
}, z.ZodType<AgentLoopParams, unknown, z.core.$ZodTypeInternals<AgentLoopParams, unknown>>, undefined, PiAgentLoopState, "agent-loop-workflow">;
|
|
19
|
+
type PiWorkflowsRegistry = {
|
|
20
|
+
agentLoop: ReturnType<typeof createPiAgentLoopWorkflow>;
|
|
21
|
+
};
|
|
22
|
+
declare const createPiWorkflows: (options: WorkflowsOptions) => {
|
|
23
|
+
agentLoop: _fragno_dev_workflows0.WorkflowDefinition<AgentLoopParams, {
|
|
24
|
+
messages: AgentMessage[];
|
|
25
|
+
}, z.ZodType<AgentLoopParams, unknown, z.core.$ZodTypeInternals<AgentLoopParams, unknown>>, undefined, PiAgentLoopState, "agent-loop-workflow">;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { PI_WORKFLOW_NAME, PiWorkflowsRegistry, createPiWorkflows };
|
|
29
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","names":[],"sources":["../../../../src/pi/workflow/workflow.ts"],"mappings":";;;;;;;;cAyCa,gBAAA;AAAA,KAER,gBAAA;EACH,MAAA,EAAQ,eAAA;EACR,KAAA,EAAO,cAAA;EACP,sBAAA,GAAyB,+BAAA;EACzB,OAAA,GAAU,gBAAA;AAAA;AAAA,cAmLN,yBAAA,GAA6B,OAAA,EAAS,gBAAA,4BAAgB,kBAAA,CAAA,eAAA;;;KAoHhD,mBAAA;EACV,SAAA,EAAW,UAAA,QAAkB,yBAAA;AAAA;AAAA,cAGlB,iBAAA,GAAqB,OAAA,EAAS,gBAAA"}
|