@adminforth/agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/agent/middleware/apiBasedTools.ts +109 -0
- package/agent/middleware/sequenceDebug.ts +302 -0
- package/agent/simpleAgent.ts +291 -0
- package/agent/skills/registry.ts +135 -0
- package/agent/systemPrompt.ts +69 -0
- package/agent/toolCallEvents.ts +17 -0
- package/agent/tools/apiTool.ts +99 -0
- package/agent/tools/fetchSkill.ts +58 -0
- package/agent/tools/fetchToolSchema.ts +50 -0
- package/agent/tools/index.ts +26 -0
- package/apiBasedTools.ts +625 -0
- package/build.log +30 -0
- package/custom/ChatSurface.vue +184 -0
- package/custom/ConversationArea.vue +175 -0
- package/custom/Message.vue +206 -0
- package/custom/SessionsHistory.vue +93 -0
- package/custom/ToolRenderer.vue +131 -0
- package/custom/ToolsGroup.vue +67 -0
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/custom/package.json +26 -0
- package/custom/pnpm-lock.yaml +1467 -0
- package/custom/skills/fetch_data/SKILL.md +15 -0
- package/custom/skills/mutate_data/SKILL.md +108 -0
- package/custom/tsconfig.json +16 -0
- package/custom/types.ts +34 -0
- package/custom/useAgentStore.ts +349 -0
- package/dist/agent/middleware/apiBasedTools.js +91 -0
- package/dist/agent/middleware/sequenceDebug.js +210 -0
- package/dist/agent/simpleAgent.js +173 -0
- package/dist/agent/skills/registry.js +108 -0
- package/dist/agent/systemPrompt.js +64 -0
- package/dist/agent/toolCallEvents.js +1 -0
- package/dist/agent/tools/apiTool.js +93 -0
- package/dist/agent/tools/fetchSkill.js +51 -0
- package/dist/agent/tools/fetchToolSchema.js +36 -0
- package/dist/agent/tools/index.js +28 -0
- package/dist/apiBasedTools.js +412 -0
- package/dist/custom/ChatSurface.vue +184 -0
- package/dist/custom/ConversationArea.vue +175 -0
- package/dist/custom/Message.vue +206 -0
- package/dist/custom/SessionsHistory.vue +93 -0
- package/dist/custom/ToolRenderer.vue +131 -0
- package/dist/custom/ToolsGroup.vue +67 -0
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/dist/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/dist/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/dist/custom/package.json +26 -0
- package/dist/custom/pnpm-lock.yaml +1467 -0
- package/dist/custom/skills/fetch_data/SKILL.md +15 -0
- package/dist/custom/skills/mutate_data/SKILL.md +108 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/types.ts +34 -0
- package/dist/custom/useAgentStore.ts +349 -0
- package/dist/index.js +415 -0
- package/dist/types.js +1 -0
- package/index.ts +457 -0
- package/package.json +58 -0
- package/tsconfig.json +13 -0
- package/types.ts +45 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
#!/bin/bash
|
|
3
|
+
|
|
4
|
+
# write npm run output both to console and to build.log
|
|
5
|
+
npm run build 2>&1 | tee build.log
|
|
6
|
+
build_status=${PIPESTATUS[0]}
|
|
7
|
+
|
|
8
|
+
# if exist status from the npm run build is not 0
|
|
9
|
+
# then exit with the status code from the npm run build
|
|
10
|
+
if [ $build_status -ne 0 ]; then
|
|
11
|
+
echo "Build failed. Exiting with status code $build_status"
|
|
12
|
+
exit $build_status
|
|
13
|
+
fi
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -x
|
|
4
|
+
|
|
5
|
+
COMMIT_SHORT_SHA=$(echo $CI_COMMIT_SHA | cut -c1-8)
|
|
6
|
+
|
|
7
|
+
STATUS=${1}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if [ "$STATUS" = "success" ]; then
|
|
11
|
+
MESSAGE="Did a build without issues on \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\`. Commit: _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
|
|
12
|
+
|
|
13
|
+
curl -s -X POST -H "Content-Type: application/json" -d '{
|
|
14
|
+
"username": "'"$CI_COMMIT_AUTHOR"'",
|
|
15
|
+
"icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
|
|
16
|
+
"attachments": [
|
|
17
|
+
{
|
|
18
|
+
"mrkdwn_in": ["text", "pretext"],
|
|
19
|
+
"color": "#36a64f",
|
|
20
|
+
"text": "'"$MESSAGE"'"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}' "$DEVELOPERS_SLACK_WEBHOOK"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
export BUILD_LOG=$(cat ./build.log)
|
|
27
|
+
|
|
28
|
+
BUILD_LOG=$(echo $BUILD_LOG | sed 's/"/\\"/g')
|
|
29
|
+
|
|
30
|
+
MESSAGE="Broke \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\` with commit _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
|
|
31
|
+
CODE_BLOCK="\`\`\`$BUILD_LOG\n\`\`\`"
|
|
32
|
+
|
|
33
|
+
echo "Sending slack message to developers $MESSAGE"
|
|
34
|
+
# Send the message
|
|
35
|
+
curl -sS -X POST -H "Content-Type: application/json" -d '{
|
|
36
|
+
"username": "'"$CI_COMMIT_AUTHOR"'",
|
|
37
|
+
"icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
|
|
38
|
+
"attachments": [
|
|
39
|
+
{
|
|
40
|
+
"mrkdwn_in": ["text", "pretext"],
|
|
41
|
+
"color": "#8A1C12",
|
|
42
|
+
"text": "'"$CODE_BLOCK"'",
|
|
43
|
+
"pretext": "'"$MESSAGE"'"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}' "$DEVELOPERS_SLACK_WEBHOOK" 2>&1
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
clone:
|
|
2
|
+
git:
|
|
3
|
+
image: woodpeckerci/plugin-git
|
|
4
|
+
settings:
|
|
5
|
+
partial: false
|
|
6
|
+
depth: 5
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
init-secrets:
|
|
10
|
+
when:
|
|
11
|
+
- event: push
|
|
12
|
+
image: infisical/cli
|
|
13
|
+
environment:
|
|
14
|
+
INFISICAL_TOKEN:
|
|
15
|
+
from_secret: VAULT_TOKEN
|
|
16
|
+
commands:
|
|
17
|
+
- infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
|
|
18
|
+
|
|
19
|
+
build:
|
|
20
|
+
image: devforth/node20-pnpm:latest
|
|
21
|
+
when:
|
|
22
|
+
- event: push
|
|
23
|
+
commands:
|
|
24
|
+
- apt update && apt install -y rsync
|
|
25
|
+
- . /woodpecker/deploy.vault.env
|
|
26
|
+
- pnpm install
|
|
27
|
+
- /bin/bash ./.woodpecker/buildRelease.sh
|
|
28
|
+
- npm audit signatures
|
|
29
|
+
|
|
30
|
+
release:
|
|
31
|
+
image: devforth/node20-pnpm:latest
|
|
32
|
+
when:
|
|
33
|
+
- event:
|
|
34
|
+
- push
|
|
35
|
+
branch:
|
|
36
|
+
- main
|
|
37
|
+
commands:
|
|
38
|
+
- . /woodpecker/deploy.vault.env
|
|
39
|
+
- pnpm exec semantic-release
|
|
40
|
+
|
|
41
|
+
slack-on-failure:
|
|
42
|
+
image: curlimages/curl
|
|
43
|
+
when:
|
|
44
|
+
- event: push
|
|
45
|
+
status: [failure]
|
|
46
|
+
commands:
|
|
47
|
+
- . /woodpecker/deploy.vault.env
|
|
48
|
+
- /bin/sh ./.woodpecker/buildSlackNotify.sh failure
|
|
49
|
+
|
|
50
|
+
slack-on-success:
|
|
51
|
+
image: curlimages/curl
|
|
52
|
+
when:
|
|
53
|
+
- event: push
|
|
54
|
+
status: [success]
|
|
55
|
+
commands:
|
|
56
|
+
- . /woodpecker/deploy.vault.env
|
|
57
|
+
- /bin/sh ./.woodpecker/buildSlackNotify.sh success
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ToolMessage } from "@langchain/core/messages";
|
|
2
|
+
import { createMiddleware } from "langchain";
|
|
3
|
+
import { logger } from "adminforth";
|
|
4
|
+
import type { ApiBasedTool } from "../../apiBasedTools.js";
|
|
5
|
+
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
|
|
6
|
+
import { createApiTool } from "../tools/apiTool.js";
|
|
7
|
+
|
|
8
|
+
function getEnabledApiToolNames(messages: unknown[]) {
|
|
9
|
+
const enabledToolNames = new Set<string>();
|
|
10
|
+
|
|
11
|
+
for (const message of messages) {
|
|
12
|
+
if (!ToolMessage.isInstance(message) || message.name !== "fetch_tool_schema") {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const content =
|
|
17
|
+
typeof message.content === "string"
|
|
18
|
+
? message.content
|
|
19
|
+
: Array.isArray(message.content)
|
|
20
|
+
? message.content
|
|
21
|
+
.map((block) =>
|
|
22
|
+
typeof block === "string"
|
|
23
|
+
? block
|
|
24
|
+
: "text" in block
|
|
25
|
+
? block.text
|
|
26
|
+
: "",
|
|
27
|
+
)
|
|
28
|
+
.join("")
|
|
29
|
+
: "";
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(content) as { status?: number; name?: string };
|
|
33
|
+
|
|
34
|
+
if (parsed.status === 200 && parsed.name) {
|
|
35
|
+
enabledToolNames.add(parsed.name);
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return enabledToolNames;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createApiBasedToolsMiddleware(
|
|
44
|
+
apiBasedTools: Record<string, ApiBasedTool>,
|
|
45
|
+
) {
|
|
46
|
+
const alwaysAvailableApiToolNames = new Set<string>(ALWAYS_AVAILABLE_API_TOOL_NAMES);
|
|
47
|
+
const dynamicTools = Object.fromEntries(
|
|
48
|
+
Object.entries(apiBasedTools).map(([toolName, apiBasedTool]) => [
|
|
49
|
+
toolName,
|
|
50
|
+
createApiTool(toolName, apiBasedTool),
|
|
51
|
+
]),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return createMiddleware({
|
|
55
|
+
name: "ApiBasedToolsMiddleware",
|
|
56
|
+
async wrapModelCall(request, handler) {
|
|
57
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
58
|
+
const tools = [...enabledApiToolNames]
|
|
59
|
+
.filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
|
|
60
|
+
.map((toolName) => dynamicTools[toolName]);
|
|
61
|
+
|
|
62
|
+
return handler({
|
|
63
|
+
...request,
|
|
64
|
+
tools: [...request.tools, ...tools],
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
async wrapToolCall(request, handler) {
|
|
68
|
+
const startedAt = Date.now();
|
|
69
|
+
const toolInput = JSON.stringify(request.toolCall.args ?? {});
|
|
70
|
+
logger.info(
|
|
71
|
+
`Invoking tool "${request.toolCall.name}" with input: ${toolInput}`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
if (request.tool) {
|
|
76
|
+
return await handler(request);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
80
|
+
|
|
81
|
+
if (enabledApiToolNames.has(request.toolCall.name)) {
|
|
82
|
+
return await handler({
|
|
83
|
+
...request,
|
|
84
|
+
tool: dynamicTools[request.toolCall.name],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return new ToolMessage({
|
|
89
|
+
content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
|
|
90
|
+
tool_call_id: request.toolCall.id ?? "",
|
|
91
|
+
name: request.toolCall.name,
|
|
92
|
+
status: "error",
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const errorDetails =
|
|
96
|
+
error instanceof Error ? error.stack ?? error.message : String(error);
|
|
97
|
+
|
|
98
|
+
logger.error(
|
|
99
|
+
`Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`,
|
|
100
|
+
);
|
|
101
|
+
throw error;
|
|
102
|
+
} finally {
|
|
103
|
+
logger.info(
|
|
104
|
+
`Tool "${request.toolCall.name}" finished in ${Date.now() - startedAt}ms`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
2
|
+
import { createMiddleware } from "langchain";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import type { ToolCallEvent } from "../toolCallEvents.js";
|
|
5
|
+
|
|
6
|
+
export type SequenceDebugResultType = "tool_calls" | "final_text";
|
|
7
|
+
|
|
8
|
+
type SequenceDebugToolCall = {
|
|
9
|
+
toolCallId: string;
|
|
10
|
+
toolName: string;
|
|
11
|
+
input: string;
|
|
12
|
+
output: string | null;
|
|
13
|
+
error: string | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type PendingSequenceDebugToolCall = SequenceDebugToolCall & {
|
|
17
|
+
completed: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type SequenceDebug = {
|
|
21
|
+
sequenceId: number;
|
|
22
|
+
startedAt: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
reasoning: string;
|
|
25
|
+
text: string;
|
|
26
|
+
toolCalls: SequenceDebugToolCall[];
|
|
27
|
+
endedAt: string;
|
|
28
|
+
resultType: SequenceDebugResultType;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type PendingSequenceDebug = Omit<SequenceDebug, "toolCalls" | "endedAt" | "resultType"> & {
|
|
32
|
+
toolCalls: PendingSequenceDebugToolCall[];
|
|
33
|
+
pendingToolCalls: number;
|
|
34
|
+
resultType: SequenceDebugResultType | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type SequenceDebugModelCall = {
|
|
38
|
+
reasoning: string;
|
|
39
|
+
text: string;
|
|
40
|
+
resultType: SequenceDebugResultType;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type SequenceDebugModelCallSink = {
|
|
44
|
+
handleModelCallStart: (prompt: string) => void;
|
|
45
|
+
handleModelCallComplete: (params: SequenceDebugModelCall) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type SequenceDebugCollector = SequenceDebugModelCallSink & {
|
|
49
|
+
handleToolCallEvent: (event: ToolCallEvent) => void;
|
|
50
|
+
flush: () => void;
|
|
51
|
+
getHistory: () => SequenceDebug[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function createPendingSequenceDebug(sequenceId: number): PendingSequenceDebug {
|
|
55
|
+
return {
|
|
56
|
+
sequenceId,
|
|
57
|
+
startedAt: new Date().toISOString(),
|
|
58
|
+
prompt: "",
|
|
59
|
+
reasoning: "",
|
|
60
|
+
text: "",
|
|
61
|
+
toolCalls: [],
|
|
62
|
+
pendingToolCalls: 0,
|
|
63
|
+
resultType: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasSequenceDebugContent(sequence: PendingSequenceDebug | null): sequence is PendingSequenceDebug {
|
|
68
|
+
return Boolean(
|
|
69
|
+
sequence &&
|
|
70
|
+
(
|
|
71
|
+
sequence.prompt ||
|
|
72
|
+
sequence.reasoning ||
|
|
73
|
+
sequence.text ||
|
|
74
|
+
sequence.toolCalls.length > 0
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function finalizeSequenceDebug(sequence: PendingSequenceDebug): SequenceDebug {
|
|
80
|
+
return {
|
|
81
|
+
sequenceId: sequence.sequenceId,
|
|
82
|
+
startedAt: sequence.startedAt,
|
|
83
|
+
prompt: sequence.prompt,
|
|
84
|
+
reasoning: sequence.reasoning,
|
|
85
|
+
text: sequence.text,
|
|
86
|
+
toolCalls: sequence.toolCalls.map(({ completed: _completed, ...toolCall }) => toolCall),
|
|
87
|
+
endedAt: new Date().toISOString(),
|
|
88
|
+
resultType: sequence.resultType ?? "final_text",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stringifyPromptForDebug(params: {
|
|
93
|
+
systemMessage: { toDict(): unknown };
|
|
94
|
+
messages: Array<{ toDict(): unknown }>;
|
|
95
|
+
tools: unknown[];
|
|
96
|
+
modelSettings?: Record<string, unknown>;
|
|
97
|
+
}) {
|
|
98
|
+
const { systemMessage, messages, tools, modelSettings } = params;
|
|
99
|
+
|
|
100
|
+
return YAML.stringify({
|
|
101
|
+
systemMessage: systemMessage.toDict(),
|
|
102
|
+
messages: messages.map((message) => message.toDict()),
|
|
103
|
+
tools: tools.map((tool) => {
|
|
104
|
+
if (
|
|
105
|
+
typeof tool === "object" &&
|
|
106
|
+
tool !== null &&
|
|
107
|
+
"name" in tool &&
|
|
108
|
+
typeof tool.name === "string"
|
|
109
|
+
) {
|
|
110
|
+
return tool.name;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
typeof tool === "object" &&
|
|
115
|
+
tool !== null &&
|
|
116
|
+
"schema" in tool &&
|
|
117
|
+
typeof tool.schema === "object" &&
|
|
118
|
+
tool.schema !== null &&
|
|
119
|
+
"name" in tool.schema &&
|
|
120
|
+
typeof tool.schema.name === "string"
|
|
121
|
+
) {
|
|
122
|
+
return tool.schema.name;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return "";
|
|
126
|
+
}),
|
|
127
|
+
...(modelSettings && Object.keys(modelSettings).length > 0
|
|
128
|
+
? { modelSettings }
|
|
129
|
+
: {}),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getMessageBlocks(message: {
|
|
134
|
+
contentBlocks?: unknown;
|
|
135
|
+
content?: unknown;
|
|
136
|
+
}) {
|
|
137
|
+
if (Array.isArray(message.contentBlocks)) {
|
|
138
|
+
return message.contentBlocks;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (Array.isArray(message.content)) {
|
|
142
|
+
return message.content;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hasToolCallSignal(message: {
|
|
149
|
+
tool_calls?: unknown;
|
|
150
|
+
tool_call_chunks?: unknown;
|
|
151
|
+
additional_kwargs?: { tool_calls?: unknown };
|
|
152
|
+
}) {
|
|
153
|
+
return Boolean(
|
|
154
|
+
(Array.isArray(message.tool_calls) && message.tool_calls.length > 0) ||
|
|
155
|
+
(Array.isArray(message.tool_call_chunks) && message.tool_call_chunks.length > 0) ||
|
|
156
|
+
(Array.isArray(message.additional_kwargs?.tool_calls) &&
|
|
157
|
+
message.additional_kwargs.tool_calls.length > 0),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCall {
|
|
162
|
+
const blocks = getMessageBlocks(message);
|
|
163
|
+
const reasoning = blocks
|
|
164
|
+
.filter((block: any) => block?.type === "reasoning")
|
|
165
|
+
.map((block: any) => String(block.reasoning ?? ""))
|
|
166
|
+
.join("");
|
|
167
|
+
const textFromBlocks = blocks
|
|
168
|
+
.filter((block: any) => block?.type === "text")
|
|
169
|
+
.map((block: any) => String(block.text ?? ""))
|
|
170
|
+
.join("");
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
reasoning,
|
|
174
|
+
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
175
|
+
resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createSequenceDebugCollector(): SequenceDebugCollector {
|
|
180
|
+
let nextSequenceId = 1;
|
|
181
|
+
let currentSequenceDebug: PendingSequenceDebug | null = null;
|
|
182
|
+
const history: SequenceDebug[] = [];
|
|
183
|
+
|
|
184
|
+
const ensureSequenceDebug = () => {
|
|
185
|
+
if (!currentSequenceDebug) {
|
|
186
|
+
currentSequenceDebug = createPendingSequenceDebug(nextSequenceId++);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return currentSequenceDebug;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const flush = () => {
|
|
193
|
+
if (!hasSequenceDebugContent(currentSequenceDebug)) {
|
|
194
|
+
currentSequenceDebug = null;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const finalizedSequenceDebug = finalizeSequenceDebug(currentSequenceDebug);
|
|
199
|
+
history.push(finalizedSequenceDebug);
|
|
200
|
+
currentSequenceDebug = null;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
handleModelCallStart(prompt) {
|
|
205
|
+
if (
|
|
206
|
+
currentSequenceDebug?.resultType &&
|
|
207
|
+
currentSequenceDebug.pendingToolCalls === 0
|
|
208
|
+
) {
|
|
209
|
+
flush();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const sequenceDebug = ensureSequenceDebug();
|
|
213
|
+
sequenceDebug.prompt = prompt;
|
|
214
|
+
},
|
|
215
|
+
handleModelCallComplete(params) {
|
|
216
|
+
const sequenceDebug = ensureSequenceDebug();
|
|
217
|
+
sequenceDebug.reasoning = params.reasoning;
|
|
218
|
+
sequenceDebug.text = params.text;
|
|
219
|
+
sequenceDebug.resultType = params.resultType;
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
sequenceDebug.resultType === "final_text" &&
|
|
223
|
+
sequenceDebug.pendingToolCalls === 0
|
|
224
|
+
) {
|
|
225
|
+
flush();
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
handleToolCallEvent(event) {
|
|
229
|
+
const sequenceDebug = ensureSequenceDebug();
|
|
230
|
+
|
|
231
|
+
if (event.phase === "start") {
|
|
232
|
+
sequenceDebug.toolCalls.push({
|
|
233
|
+
toolCallId: event.toolCallId,
|
|
234
|
+
toolName: event.toolName,
|
|
235
|
+
input: event.input,
|
|
236
|
+
output: null,
|
|
237
|
+
error: null,
|
|
238
|
+
completed: false,
|
|
239
|
+
});
|
|
240
|
+
sequenceDebug.pendingToolCalls += 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const pendingToolCall = sequenceDebug.toolCalls.find(
|
|
245
|
+
(toolCall) => toolCall.toolCallId === event.toolCallId && !toolCall.completed,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (pendingToolCall) {
|
|
249
|
+
pendingToolCall.output = event.output;
|
|
250
|
+
pendingToolCall.error = event.error;
|
|
251
|
+
pendingToolCall.completed = true;
|
|
252
|
+
} else {
|
|
253
|
+
sequenceDebug.toolCalls.push({
|
|
254
|
+
toolCallId: event.toolCallId,
|
|
255
|
+
toolName: event.toolName,
|
|
256
|
+
input: "",
|
|
257
|
+
output: event.output,
|
|
258
|
+
error: event.error,
|
|
259
|
+
completed: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
sequenceDebug.pendingToolCalls = Math.max(
|
|
264
|
+
0,
|
|
265
|
+
sequenceDebug.pendingToolCalls - 1,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
sequenceDebug.resultType === "tool_calls" &&
|
|
270
|
+
sequenceDebug.pendingToolCalls === 0
|
|
271
|
+
) {
|
|
272
|
+
flush();
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
flush,
|
|
276
|
+
getHistory() {
|
|
277
|
+
return history;
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function createSequenceDebugMiddleware(
|
|
283
|
+
sink: SequenceDebugModelCallSink,
|
|
284
|
+
) {
|
|
285
|
+
return createMiddleware({
|
|
286
|
+
name: "SequenceDebugMiddleware",
|
|
287
|
+
async wrapModelCall(request, handler) {
|
|
288
|
+
sink.handleModelCallStart(
|
|
289
|
+
stringifyPromptForDebug({
|
|
290
|
+
systemMessage: request.systemMessage,
|
|
291
|
+
messages: request.messages,
|
|
292
|
+
tools: request.tools,
|
|
293
|
+
modelSettings: request.modelSettings,
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const response = await handler(request) as AIMessage;
|
|
298
|
+
sink.handleModelCallComplete(extractSequenceResponseDebug(response));
|
|
299
|
+
return response;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|