@andreprado/agentkit 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +4 -0
- package/docs/guides/connect-telegram.md +2 -0
- package/docs/guides/create-agent.md +9 -0
- package/docs/guides/run-evals.md +36 -1
- package/package.json +1 -1
- package/src/cli/cloud-client.ts +10 -2
- package/src/cli/commands/channels.ts +28 -2
- package/src/cli/deploy-chat-ui.ts +7 -0
- package/src/cli/help.ts +18 -2
- package/src/cli/index.ts +124 -4
- package/src/index.ts +44 -1
- package/src/providers/pi.ts +1 -1
- package/src/providers/test.ts +1 -1
- package/src/runtime/config.ts +8 -0
- package/src/runtime/database.ts +93 -2
- package/src/runtime/db-commands.ts +9 -0
- package/src/runtime/dev-server.ts +43 -2
- package/src/runtime/evals.ts +210 -20
- package/src/runtime/inspect.ts +5 -0
- package/src/runtime/spec.ts +152 -0
- package/src/runtime/sync.ts +144 -0
- package/src/runtime/targets/cloudflare/build.ts +45 -0
- package/src/runtime/tools.ts +121 -1
- package/src/runtime/traces.ts +41 -0
- package/src/storage/sqlite.ts +136 -0
- package/src/templates/blank.ts +8 -1
- package/src/templates/dentista.ts +14 -1
- package/src/templates/skills/agentkit-build-agent/SKILL.md +9 -7
- package/src/templates/skills/agentkit-database/SKILL.md +5 -2
- package/src/templates/skills/agentkit-evals/SKILL.md +32 -3
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
- package/src/templates/support.ts +7 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { findAgentCapsuleRoot } from "./config";
|
|
5
|
+
import { AgentKitError } from "./errors";
|
|
6
|
+
|
|
7
|
+
export type AgentSpecInitResult = {
|
|
8
|
+
root: string;
|
|
9
|
+
file: string;
|
|
10
|
+
created: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const REQUIRED_SPEC_HEADINGS = [
|
|
14
|
+
"Objective",
|
|
15
|
+
"Primary Flows",
|
|
16
|
+
"Required Data",
|
|
17
|
+
"Tools And Integrations",
|
|
18
|
+
"Knowledge",
|
|
19
|
+
"Handoff And Escalation",
|
|
20
|
+
"Limits",
|
|
21
|
+
"Acceptance Criteria",
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export async function initializeAgentSpec(
|
|
25
|
+
cwd: string,
|
|
26
|
+
input: {
|
|
27
|
+
brief: string;
|
|
28
|
+
out?: string;
|
|
29
|
+
force?: boolean;
|
|
30
|
+
},
|
|
31
|
+
): Promise<AgentSpecInitResult> {
|
|
32
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
33
|
+
const brief = input.brief.trim();
|
|
34
|
+
|
|
35
|
+
if (!brief) {
|
|
36
|
+
throw new AgentKitError("validation_error", "agentkit spec init requires a non-empty --brief value.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const file = resolve(root, input.out ?? "AGENT_SPEC.md");
|
|
40
|
+
|
|
41
|
+
if (!input.force && await pathExists(file)) {
|
|
42
|
+
throw new AgentKitError(
|
|
43
|
+
"validation_error",
|
|
44
|
+
`${relative(root, file)} already exists. Update it directly, re-run with --force, or choose --out <path>.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await mkdir(dirname(file), { recursive: true });
|
|
49
|
+
await writeFile(file, renderAgentSpec(brief));
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
root,
|
|
53
|
+
file,
|
|
54
|
+
created: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function checkAgentSpec(cwd: string, fileName = "AGENT_SPEC.md"): Promise<{
|
|
59
|
+
root: string;
|
|
60
|
+
file: string;
|
|
61
|
+
ok: boolean;
|
|
62
|
+
missingHeadings: string[];
|
|
63
|
+
}> {
|
|
64
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
65
|
+
const file = resolve(root, fileName);
|
|
66
|
+
let source: string;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
source = await readFile(file, "utf8");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new AgentKitError(
|
|
72
|
+
"validation_error",
|
|
73
|
+
`${relative(root, file)} was not found. Run agentkit spec init --brief "<owner request>" after the owner gives the agent idea.`,
|
|
74
|
+
{ cause: error },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const missingHeadings = REQUIRED_SPEC_HEADINGS.filter((heading) => !source.includes(`## ${heading}`));
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
root,
|
|
82
|
+
file,
|
|
83
|
+
ok: missingHeadings.length === 0,
|
|
84
|
+
missingHeadings,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderAgentSpec(brief: string): string {
|
|
89
|
+
return `# Agent Spec
|
|
90
|
+
|
|
91
|
+
## Owner Request
|
|
92
|
+
|
|
93
|
+
${brief}
|
|
94
|
+
|
|
95
|
+
## Objective
|
|
96
|
+
|
|
97
|
+
Define the agent's job in one or two concrete sentences before editing prompts or tools.
|
|
98
|
+
|
|
99
|
+
## Primary Flows
|
|
100
|
+
|
|
101
|
+
- Flow 1:
|
|
102
|
+
- Flow 2:
|
|
103
|
+
- Flow 3:
|
|
104
|
+
|
|
105
|
+
## Required Data
|
|
106
|
+
|
|
107
|
+
- User data to collect:
|
|
108
|
+
- Business data to read:
|
|
109
|
+
- Records to create or update:
|
|
110
|
+
|
|
111
|
+
## Tools And Integrations
|
|
112
|
+
|
|
113
|
+
- Tools to add:
|
|
114
|
+
- External systems:
|
|
115
|
+
- Secrets needed:
|
|
116
|
+
|
|
117
|
+
## Knowledge
|
|
118
|
+
|
|
119
|
+
- Documents, FAQs, catalogs, CSVs, or policies to add:
|
|
120
|
+
- Sync source, if any:
|
|
121
|
+
|
|
122
|
+
## Handoff And Escalation
|
|
123
|
+
|
|
124
|
+
- When the agent should stop and hand off:
|
|
125
|
+
- What information the handoff must include:
|
|
126
|
+
|
|
127
|
+
## Limits
|
|
128
|
+
|
|
129
|
+
- The agent must not:
|
|
130
|
+
- Safety, compliance, or authorization boundaries:
|
|
131
|
+
|
|
132
|
+
## Acceptance Criteria
|
|
133
|
+
|
|
134
|
+
- [ ] Prompt updated for the domain and boundaries.
|
|
135
|
+
- [ ] Tools added for actions, live data, or durable writes.
|
|
136
|
+
- [ ] Knowledge or seed data added when the agent needs catalog facts.
|
|
137
|
+
- [ ] Multi-turn eval covers the main user flow.
|
|
138
|
+
- [ ] Direct tool checks pass for every new tool.
|
|
139
|
+
- [ ] \`npm run typecheck\` passes.
|
|
140
|
+
- [ ] \`npm run agentkit -- inspect\` passes.
|
|
141
|
+
- [ ] \`npm run eval\` passes.
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
146
|
+
try {
|
|
147
|
+
await stat(path);
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import type { SyncContext } from "../index";
|
|
6
|
+
import { findAgentCapsuleRoot, loadAgentCapsule } from "./config";
|
|
7
|
+
import { createLocalDatabaseRunner, openPreparedCapsuleStore } from "./database";
|
|
8
|
+
import { loadCapsuleEnv } from "./env";
|
|
9
|
+
import { AgentKitError } from "./errors";
|
|
10
|
+
|
|
11
|
+
export type SyncRunResult = {
|
|
12
|
+
root: string;
|
|
13
|
+
file: string;
|
|
14
|
+
output: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type SyncInitResult = {
|
|
18
|
+
root: string;
|
|
19
|
+
files: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type SyncModule = {
|
|
23
|
+
default?: unknown;
|
|
24
|
+
sync?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function runCapsuleSync(cwd: string, input: { file?: string } = {}): Promise<SyncRunResult> {
|
|
28
|
+
const capsule = await loadAgentCapsule(cwd);
|
|
29
|
+
const file = resolve(capsule.root, input.file ?? "sync.ts");
|
|
30
|
+
const sync = await loadSyncFunction(file);
|
|
31
|
+
const env = await loadCapsuleEnv(capsule.root);
|
|
32
|
+
const prepared = await openPreparedCapsuleStore(capsule);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const db = createLocalDatabaseRunner(prepared.store);
|
|
36
|
+
const context: SyncContext = {
|
|
37
|
+
env,
|
|
38
|
+
database: db,
|
|
39
|
+
db,
|
|
40
|
+
runtime: {
|
|
41
|
+
environment: "local",
|
|
42
|
+
invocation: "sync",
|
|
43
|
+
target: "bun",
|
|
44
|
+
database: "sqlite",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const output = await sync(context);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
root: capsule.root,
|
|
51
|
+
file,
|
|
52
|
+
output: output ?? { ok: true },
|
|
53
|
+
};
|
|
54
|
+
} finally {
|
|
55
|
+
prepared.store.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function initializeSyncScaffold(cwd: string, input: { force?: boolean } = {}): Promise<SyncInitResult> {
|
|
60
|
+
const root = await findAgentCapsuleRoot(cwd);
|
|
61
|
+
const files = [
|
|
62
|
+
{
|
|
63
|
+
path: "sync.ts",
|
|
64
|
+
contents: `import type { SyncContext } from "@andreprado/agentkit";
|
|
65
|
+
|
|
66
|
+
export default async function sync(ctx: SyncContext) {
|
|
67
|
+
// Replace this with catalog/API import logic. Use ctx.db for durable writes.
|
|
68
|
+
await ctx.db.execute(
|
|
69
|
+
"INSERT OR IGNORE INTO agent_notes (id, content) VALUES (?, ?)",
|
|
70
|
+
["seed-note", "Created by sync.ts"],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
recordsImported: 1,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
path: "seed.sql",
|
|
82
|
+
contents: `INSERT OR IGNORE INTO agent_notes (id, content)
|
|
83
|
+
VALUES ('seed-note', 'Created by seed.sql');
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
path: "migrations/0001_initial.sql",
|
|
88
|
+
contents: `CREATE TABLE IF NOT EXISTS agent_notes (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
content TEXT NOT NULL,
|
|
91
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
92
|
+
);
|
|
93
|
+
`,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
const written: string[] = [];
|
|
97
|
+
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
const absolutePath = resolve(root, file.path);
|
|
100
|
+
|
|
101
|
+
if (!input.force && await pathExists(absolutePath)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
106
|
+
await writeFile(absolutePath, file.contents);
|
|
107
|
+
written.push(file.path);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { root, files: written };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function loadSyncFunction(file: string): Promise<(context: SyncContext) => Promise<unknown> | unknown> {
|
|
114
|
+
let module: SyncModule;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const url = pathToFileURL(file);
|
|
118
|
+
url.searchParams.set("t", String(Date.now()));
|
|
119
|
+
module = (await import(url.href)) as SyncModule;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new AgentKitError(
|
|
122
|
+
"validation_error",
|
|
123
|
+
`Could not load sync file at ${relative(process.cwd(), file)}. Run agentkit sync init or pass --file <path>.`,
|
|
124
|
+
{ cause: error },
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sync = module.default ?? module.sync;
|
|
129
|
+
|
|
130
|
+
if (typeof sync !== "function") {
|
|
131
|
+
throw new AgentKitError("validation_error", `${relative(process.cwd(), file)} must export a default sync function.`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return sync as (context: SyncContext) => Promise<unknown> | unknown;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
138
|
+
try {
|
|
139
|
+
await stat(path);
|
|
140
|
+
return true;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -751,6 +751,10 @@ export default {
|
|
|
751
751
|
return Response.json({ status: "ok", agent: manifest.name });
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
+
if (isHostedChannelWebhookPath(url.pathname)) {
|
|
755
|
+
return forwardHostedChannelWebhook(request, env, url);
|
|
756
|
+
}
|
|
757
|
+
|
|
754
758
|
if (request.method === "POST" && (url.pathname === "/chat" || url.pathname === "/v1/chat")) {
|
|
755
759
|
return handleChat(request, env);
|
|
756
760
|
}
|
|
@@ -775,6 +779,47 @@ export default {
|
|
|
775
779
|
},
|
|
776
780
|
};
|
|
777
781
|
|
|
782
|
+
function isHostedChannelWebhookPath(pathname) {
|
|
783
|
+
return (
|
|
784
|
+
/^\\/channels\\/[^/]+\\/(?:website|telegram)\\/webhook$/.test(pathname) ||
|
|
785
|
+
/^\\/channels\\/[^/]+\\/whatsapp\\/(?:zapster|meta)\\/webhook$/.test(pathname)
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async function forwardHostedChannelWebhook(request, env, url) {
|
|
790
|
+
if (!env.AGENTKIT_CLOUD_API_URL) {
|
|
791
|
+
return Response.json(
|
|
792
|
+
{
|
|
793
|
+
error: {
|
|
794
|
+
code: "channel_webhook_proxy_not_configured",
|
|
795
|
+
message: "Hosted channel webhooks require AGENTKIT_CLOUD_API_URL on the deployed Worker.",
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
{ status: 501 },
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const target = new URL(\`\${url.pathname}\${url.search}\`, env.AGENTKIT_CLOUD_API_URL);
|
|
803
|
+
const headers = new Headers(request.headers);
|
|
804
|
+
headers.delete("host");
|
|
805
|
+
headers.delete("content-length");
|
|
806
|
+
|
|
807
|
+
if (env.AGENTKIT_DEPLOY_ID) {
|
|
808
|
+
headers.set("X-AgentKit-Deploy-ID", env.AGENTKIT_DEPLOY_ID);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (env.AGENTKIT_RUNTIME_GATE_TOKEN) {
|
|
812
|
+
headers.set("X-AgentKit-Runtime-Gate", env.AGENTKIT_RUNTIME_GATE_TOKEN);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return fetch(target.href, {
|
|
816
|
+
method: request.method,
|
|
817
|
+
headers,
|
|
818
|
+
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
|
|
819
|
+
redirect: "manual",
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
778
823
|
async function handleTursoHealth(request, env) {
|
|
779
824
|
const auth = authorizeAdmin(request, env);
|
|
780
825
|
|
package/src/runtime/tools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool, JsonSchema, ToolContext, ToolRuntimeContext } from "../index";
|
|
1
|
+
import type { AgentTool, JsonSchema, ToolContext, ToolRenderResult, ToolRuntimeContext } from "../index";
|
|
2
2
|
import type { SqliteAgentKitStore } from "../storage/sqlite";
|
|
3
3
|
import { createLocalDatabaseRunner } from "./database";
|
|
4
4
|
import { AgentKitError } from "./errors";
|
|
@@ -14,6 +14,7 @@ export type ToolCallResult = {
|
|
|
14
14
|
visibility: "user" | "internal";
|
|
15
15
|
input: unknown;
|
|
16
16
|
output: unknown;
|
|
17
|
+
rendered?: ToolRenderResult;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export type ToolRuntime = {
|
|
@@ -97,10 +98,13 @@ async function executeToolCall(
|
|
|
97
98
|
validateSchema(output, tool.outputSchema, `tool "${tool.name}" output`);
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
const rendered = renderToolOutput(tool, input, output, options.runtime);
|
|
102
|
+
|
|
100
103
|
options.store.completeToolCall({
|
|
101
104
|
toolCallId: toolCall.id,
|
|
102
105
|
status: "completed",
|
|
103
106
|
output,
|
|
107
|
+
rendered,
|
|
104
108
|
secretValues,
|
|
105
109
|
});
|
|
106
110
|
|
|
@@ -110,6 +114,7 @@ async function executeToolCall(
|
|
|
110
114
|
visibility: tool.visibility ?? "user",
|
|
111
115
|
input,
|
|
112
116
|
output,
|
|
117
|
+
...(rendered ? { rendered } : {}),
|
|
113
118
|
};
|
|
114
119
|
} catch (error) {
|
|
115
120
|
const secretValues = collectConfiguredSecretValues(tool, options.env ?? process.env);
|
|
@@ -125,6 +130,121 @@ async function executeToolCall(
|
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
function renderToolOutput(
|
|
134
|
+
tool: AgentTool,
|
|
135
|
+
input: unknown,
|
|
136
|
+
output: unknown,
|
|
137
|
+
runtime: ToolRuntimeContext,
|
|
138
|
+
): ToolRenderResult | undefined {
|
|
139
|
+
if (!tool.render) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rendered = tool.render(output, {
|
|
144
|
+
input,
|
|
145
|
+
runtime,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (typeof rendered === "string") {
|
|
149
|
+
return { text: rendered };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!isRecord(rendered) || typeof rendered.text !== "string" || rendered.text.trim().length === 0) {
|
|
153
|
+
throw new AgentKitError("tool_validation_error", `tool "${tool.name}" render must return a non-empty text string.`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (rendered.blocks !== undefined) {
|
|
157
|
+
if (!Array.isArray(rendered.blocks)) {
|
|
158
|
+
throw new AgentKitError("tool_validation_error", `tool "${tool.name}" render.blocks must be an array when provided.`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const [index, block] of rendered.blocks.entries()) {
|
|
162
|
+
validateRenderBlock(block, `tool "${tool.name}" render.blocks[${index}]`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
text: rendered.text,
|
|
168
|
+
...(rendered.blocks ? { blocks: rendered.blocks } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateRenderBlock(block: unknown, label: string): void {
|
|
173
|
+
if (!isRecord(block)) {
|
|
174
|
+
throw new AgentKitError("tool_validation_error", `${label} must be an object.`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (block.type === "text") {
|
|
178
|
+
if (typeof block.text !== "string" || block.text.trim().length === 0) {
|
|
179
|
+
throw new AgentKitError("tool_validation_error", `${label}.text must be a non-empty string.`);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (block.type === "card") {
|
|
185
|
+
if (typeof block.title !== "string" || block.title.trim().length === 0) {
|
|
186
|
+
throw new AgentKitError("tool_validation_error", `${label}.title must be a non-empty string.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
validateOptionalString(block.subtitle, `${label}.subtitle`);
|
|
190
|
+
validateRenderFields(block.fields, `${label}.fields`);
|
|
191
|
+
validateRenderActions(block.actions, `${label}.actions`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
throw new AgentKitError("tool_validation_error", `${label}.type must be "text" or "card".`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function validateRenderFields(value: unknown, label: string): void {
|
|
199
|
+
if (value === undefined) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!Array.isArray(value)) {
|
|
204
|
+
throw new AgentKitError("tool_validation_error", `${label} must be an array when provided.`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const [index, field] of value.entries()) {
|
|
208
|
+
if (!isRecord(field)) {
|
|
209
|
+
throw new AgentKitError("tool_validation_error", `${label}[${index}] must be an object.`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
validateRequiredString(field.label, `${label}[${index}].label`);
|
|
213
|
+
validateRequiredString(field.value, `${label}[${index}].value`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function validateRenderActions(value: unknown, label: string): void {
|
|
218
|
+
if (value === undefined) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!Array.isArray(value)) {
|
|
223
|
+
throw new AgentKitError("tool_validation_error", `${label} must be an array when provided.`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const [index, action] of value.entries()) {
|
|
227
|
+
if (!isRecord(action)) {
|
|
228
|
+
throw new AgentKitError("tool_validation_error", `${label}[${index}] must be an object.`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
validateRequiredString(action.label, `${label}[${index}].label`);
|
|
232
|
+
validateOptionalString(action.href, `${label}[${index}].href`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function validateRequiredString(value: unknown, label: string): void {
|
|
237
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
238
|
+
throw new AgentKitError("tool_validation_error", `${label} must be a non-empty string.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validateOptionalString(value: unknown, label: string): void {
|
|
243
|
+
if (value !== undefined && typeof value !== "string") {
|
|
244
|
+
throw new AgentKitError("tool_validation_error", `${label} must be a string when provided.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
128
248
|
function resolveToolSecrets(
|
|
129
249
|
tool: AgentTool,
|
|
130
250
|
env: Record<string, string | undefined>,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ConversationRecord, StoredRun, StoredToolCall } from "../storage/sqlite";
|
|
2
|
+
import { openCapsuleStore } from "../storage/sqlite";
|
|
3
|
+
import { loadAgentCapsule } from "./config";
|
|
4
|
+
import { AgentKitError } from "./errors";
|
|
5
|
+
|
|
6
|
+
export type ConversationTrace = ConversationRecord & {
|
|
7
|
+
runs: ConversationTraceRun[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ConversationTraceRun = StoredRun & {
|
|
11
|
+
toolCalls: StoredToolCall[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function getConversationTraceFromCwd(cwd: string, conversationId: string): Promise<ConversationTrace> {
|
|
15
|
+
if (!conversationId) {
|
|
16
|
+
throw new AgentKitError("validation_error", "Missing conversation id.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const capsule = await loadAgentCapsule(cwd);
|
|
20
|
+
const store = await openCapsuleStore(capsule);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const conversation = store.getConversation(conversationId);
|
|
24
|
+
|
|
25
|
+
if (!conversation) {
|
|
26
|
+
throw new AgentKitError("conversation_not_found", `Conversation "${conversationId}" was not found.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const runs = store.listRunsForConversation(conversation.id).map((run) => ({
|
|
30
|
+
...run,
|
|
31
|
+
toolCalls: store.listToolCallsForRun(run.id),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...conversation,
|
|
36
|
+
runs,
|
|
37
|
+
};
|
|
38
|
+
} finally {
|
|
39
|
+
store.close();
|
|
40
|
+
}
|
|
41
|
+
}
|