@andreprado/agentkit 0.1.0-alpha.16 → 0.1.0-alpha.18
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 +8 -0
- package/docs/guides/add-channel.md +4 -2
- package/docs/guides/add-managed-composio.md +161 -0
- package/docs/guides/channel-security.md +36 -39
- package/docs/guides/connect-telegram.md +3 -1
- package/docs/guides/debug-channel.md +141 -0
- package/docs/guides/prepare-deploy.md +7 -10
- package/docs/llms-full.txt +19 -5
- package/docs/llms.txt +11 -2
- package/package.json +1 -3
- package/src/cli/cloud-client.ts +12 -0
- package/src/cli/commands/channels.ts +119 -7
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/deploy-chat-ui.ts +146 -3
- package/src/cli/deploy-readiness.ts +112 -3
- package/src/cli/help.ts +19 -4
- package/src/cli/index.ts +198 -3
- package/src/create-project.ts +4 -32
- package/src/index.ts +149 -0
- package/src/runtime/chat.ts +2 -1
- package/src/runtime/config.ts +133 -0
- package/src/runtime/core/manifest.ts +31 -2
- package/src/runtime/deploy-readiness.ts +31 -1
- package/src/runtime/inspect.ts +109 -4
- package/src/runtime/integrations/composio.ts +423 -0
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +395 -10
- package/src/runtime/tool-runner.ts +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +2 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +3 -1
- package/src/templates/skills/agentkit-channels/references/telegram.md +3 -1
- package/src/templates/skills/agentkit-deploy/SKILL.md +1 -1
- package/src/templates/skills/agentkit-integrations/SKILL.md +75 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +3 -2
- package/docs/guides/agentkit-skills-architecture.md +0 -472
- package/docs/guides/channels-implementation-map.md +0 -243
- package/docs/guides/channels-production-handoff.md +0 -118
- package/docs/portable-deploy-release-checklist.md +0 -41
package/docs/llms.txt
CHANGED
|
@@ -16,13 +16,12 @@ Task guides:
|
|
|
16
16
|
- Build container artifact: `agentkit build --target container`
|
|
17
17
|
- Generate VPS handoff: `agentkit deploy --target vps --host agent.example.com --dry-run`
|
|
18
18
|
- Add hosted channels: `docs/guides/add-channel.md`
|
|
19
|
+
- Add AgentKit-managed Composio: `docs/guides/add-managed-composio.md`
|
|
19
20
|
- Buffer rapid channel messages: `docs/guides/add-channel.md#buffer-bursty-messages`
|
|
20
21
|
- Connect Telegram: `docs/guides/connect-telegram.md`
|
|
21
22
|
- Connect WhatsApp through Zapster: `docs/guides/connect-whatsapp-zapster.md`
|
|
22
23
|
- Follow channel webhook and delivery-log safety rules: `docs/guides/channel-security.md`
|
|
23
|
-
- Prepare Channels for production: `docs/guides/channels-production-handoff.md`
|
|
24
24
|
- Follow secret, access, and tool safety rules: `docs/guides/security-rules.md`
|
|
25
|
-
- Plan repo-local skills for coding agents: `docs/guides/agentkit-skills-architecture.md`
|
|
26
25
|
|
|
27
26
|
Current local commands:
|
|
28
27
|
|
|
@@ -43,12 +42,19 @@ agentkit knowledge search <query> [--top-k <number>]
|
|
|
43
42
|
agentkit eval run
|
|
44
43
|
agentkit conversations list
|
|
45
44
|
agentkit conversations show <conversation-id>
|
|
45
|
+
agentkit conversations trace <conversation-id> [--deploy]
|
|
46
46
|
agentkit channels list
|
|
47
47
|
agentkit channels add telegram support-telegram
|
|
48
48
|
agentkit channels setup support-telegram
|
|
49
49
|
agentkit channels status support-telegram
|
|
50
50
|
agentkit channels test support-telegram --message "hello"
|
|
51
|
+
agentkit channels test-audio support-telegram --fixture voice-note
|
|
52
|
+
agentkit transcribe smoke --provider groq
|
|
51
53
|
agentkit channels deliveries list support-telegram
|
|
54
|
+
agentkit integrations status [--toolkit googlecalendar]
|
|
55
|
+
agentkit integrations connect composio --toolkit gmail
|
|
56
|
+
agentkit skills status
|
|
57
|
+
agentkit skills sync
|
|
52
58
|
agentkit login --token agk_user_...
|
|
53
59
|
agentkit deploy doctor
|
|
54
60
|
agentkit deploy --smoke "hello"
|
|
@@ -69,6 +75,8 @@ Generated capsules include `AGENTKIT.md`, `AGENTS.md`, and a repo-local `skills/
|
|
|
69
75
|
|
|
70
76
|
UI testing is part of the handoff. For local UI testing, run `agentkit dev`, open the printed `Chat:` URL, and tell the owner the exact URL. After hosted deploy, run `agentkit chat-ui --deploy`, open the printed `Chat:` URL, and tell the owner it is connected to the deploy.
|
|
71
77
|
|
|
78
|
+
Hosted Chat UI shows the current conversation id, tool calls, tool errors, and a new-conversation control. Use `agentkit conversations trace <conversation-id> --deploy` to pull the hosted trace from the last deploy.
|
|
79
|
+
|
|
72
80
|
`test/fake` is deterministic and validates scaffold, direct tool calls, and fake-provider evals. It does not validate natural conversation quality. Before claiming real conversation behavior has been tested, ask the owner which provider to use: OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them.
|
|
73
81
|
|
|
74
82
|
AgentKit injects the current ISO timestamp, local date, weekday, local date/time, and timezone dynamically into every chat run. Set `timeZone` in `agentkit.config.ts` for scheduling agents so "today", "tomorrow", and weekdays resolve in the business/user timezone; otherwise AgentKit falls back to `AGENTKIT_TIME_ZONE`, valid `TZ`, then the runtime default. Do not hardcode today's date in prompts.
|
|
@@ -80,6 +88,7 @@ GET /_agentkit
|
|
|
80
88
|
POST /v1/chat
|
|
81
89
|
GET /v1/conversations
|
|
82
90
|
GET /v1/conversations/:id
|
|
91
|
+
GET /v1/conversations/:id/trace
|
|
83
92
|
```
|
|
84
93
|
|
|
85
94
|
Local `.env` is for development only. Use `.env.schema` as the committed secret-name contract; local AgentKit commands load `.env` directly. Hosted alpha deploys require `cloudflare_deploy_alpha` through `agentkit login --token ...`; production uses managed secrets in the hosted contract.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andreprado/agentkit",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.18",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
"!src/**/*.test.ts",
|
|
19
19
|
"!src/runtime/fixtures",
|
|
20
20
|
"docs",
|
|
21
|
-
"!docs/guides/backend-contracts.md",
|
|
22
|
-
"!docs/guides/debug-channel.md",
|
|
23
21
|
"README.md"
|
|
24
22
|
],
|
|
25
23
|
"publishConfig": {
|
package/src/cli/cloud-client.ts
CHANGED
|
@@ -39,6 +39,18 @@ export type CloudLimitsResponse = {
|
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
export type CloudManagedComposioResponse = {
|
|
43
|
+
managed_composio?: {
|
|
44
|
+
entitlement?: "active" | "missing" | string;
|
|
45
|
+
api_key?: "set" | "missing" | string;
|
|
46
|
+
toolkits?: Array<{
|
|
47
|
+
toolkit?: string;
|
|
48
|
+
auth_config_env?: string;
|
|
49
|
+
auth_config?: "set" | "missing" | string;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
42
54
|
export type CloudProjectResolveResponse = {
|
|
43
55
|
project?: {
|
|
44
56
|
id?: string;
|
|
@@ -162,8 +162,10 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
162
162
|
let channel = created.channel;
|
|
163
163
|
printChannelStatus(channel);
|
|
164
164
|
printSecretRepairHint(channel);
|
|
165
|
+
console.log("Channel resource: created_or_updated");
|
|
165
166
|
|
|
166
167
|
if (hasMissingRequiredSecrets(channel)) {
|
|
168
|
+
console.log("Secrets: missing");
|
|
167
169
|
console.log("");
|
|
168
170
|
console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
|
|
169
171
|
console.log("Run: npm run agentkit -- secret sync --from-local");
|
|
@@ -171,31 +173,40 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
171
173
|
return;
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
console.log("Secrets: validated");
|
|
177
|
+
|
|
174
178
|
if (channel.type === "telegram") {
|
|
175
179
|
const setup = await applyTelegramWebhook(channel, args.flags.api);
|
|
176
180
|
channel = setup.channel ?? channel;
|
|
181
|
+
console.log("Telegram setWebhook: OK");
|
|
177
182
|
console.log("Telegram webhook confirmed.");
|
|
178
183
|
if (setup.setup?.get_webhook_info) {
|
|
179
184
|
console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
|
|
183
|
-
console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
187
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
184
188
|
printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
|
|
189
|
+
if (!smoke) {
|
|
190
|
+
process.exitCode = 1;
|
|
191
|
+
}
|
|
185
192
|
return;
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
189
|
-
const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
|
|
190
|
-
console.log(`Webhook smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
196
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
191
197
|
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
198
|
+
if (!smoke) {
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
192
201
|
} else if (channel.type === "whatsapp") {
|
|
193
202
|
console.log("Smoke: skipped until the provider webhook is configured.");
|
|
194
203
|
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
195
204
|
} else {
|
|
196
|
-
const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
|
|
197
|
-
console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
205
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
198
206
|
console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
|
|
207
|
+
if (!smoke) {
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
}
|
|
199
210
|
}
|
|
200
211
|
return;
|
|
201
212
|
}
|
|
@@ -274,6 +285,25 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
274
285
|
return;
|
|
275
286
|
}
|
|
276
287
|
|
|
288
|
+
if (subcommand === "test-audio") {
|
|
289
|
+
if (!first) {
|
|
290
|
+
throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
294
|
+
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
295
|
+
const body = await channelAudioTestBody(channel, args.flags.fixture);
|
|
296
|
+
const signed = await runServerSignedChannelTest(channel, apiUrl, body);
|
|
297
|
+
const response = signed.response;
|
|
298
|
+
const payload = signed.payload;
|
|
299
|
+
|
|
300
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
301
|
+
if (!response.ok) {
|
|
302
|
+
process.exitCode = 1;
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
277
307
|
if (subcommand === "deliveries") {
|
|
278
308
|
if (first === "list") {
|
|
279
309
|
if (!second) {
|
|
@@ -367,7 +397,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
367
397
|
}
|
|
368
398
|
|
|
369
399
|
throw new Error(
|
|
370
|
-
"Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
|
|
400
|
+
"Usage: agentkit channels list | add | connect | setup | status | doctor | test | test-audio | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
|
|
371
401
|
);
|
|
372
402
|
}
|
|
373
403
|
|
|
@@ -419,6 +449,20 @@ async function runChannelSmoke(
|
|
|
419
449
|
return { status: "unknown" };
|
|
420
450
|
}
|
|
421
451
|
|
|
452
|
+
async function runConnectSmoke(
|
|
453
|
+
run: () => Promise<{ status: string; delivery_id?: string }>,
|
|
454
|
+
): Promise<{ status: string; delivery_id?: string } | null> {
|
|
455
|
+
try {
|
|
456
|
+
const smoke = await run();
|
|
457
|
+
console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
458
|
+
return smoke;
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.log("Synthetic smoke: failed");
|
|
461
|
+
console.log(` ${formatCommandError(error)}`);
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
422
466
|
async function runServerSignedChannelTest(
|
|
423
467
|
channel: CloudChannel,
|
|
424
468
|
apiUrl: string,
|
|
@@ -601,6 +645,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
601
645
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
602
646
|
}
|
|
603
647
|
|
|
648
|
+
function formatCommandError(error: unknown): string {
|
|
649
|
+
return error instanceof Error ? error.message : String(error);
|
|
650
|
+
}
|
|
651
|
+
|
|
604
652
|
async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
|
|
605
653
|
return readLocalDeployStateIfExists(cwd);
|
|
606
654
|
}
|
|
@@ -974,6 +1022,70 @@ async function channelTestBody(
|
|
|
974
1022
|
});
|
|
975
1023
|
}
|
|
976
1024
|
|
|
1025
|
+
async function channelAudioTestBody(
|
|
1026
|
+
channel: CloudChannel,
|
|
1027
|
+
fixtureFlag: string | boolean | undefined,
|
|
1028
|
+
): Promise<string> {
|
|
1029
|
+
if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
|
|
1030
|
+
return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
|
|
1034
|
+
|
|
1035
|
+
if (channel.type === "telegram") {
|
|
1036
|
+
if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
|
|
1037
|
+
throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return JSON.stringify({
|
|
1041
|
+
update_id: Date.now(),
|
|
1042
|
+
message: {
|
|
1043
|
+
message_id: 1,
|
|
1044
|
+
date: Math.floor(Date.now() / 1000),
|
|
1045
|
+
chat: { id: 456, type: "private" },
|
|
1046
|
+
from: { id: 123, first_name: "AgentKit" },
|
|
1047
|
+
voice: {
|
|
1048
|
+
file_id: `agentkit_voice_${Date.now()}`,
|
|
1049
|
+
file_unique_id: `agentkit_voice_unique_${Date.now()}`,
|
|
1050
|
+
duration: 12,
|
|
1051
|
+
mime_type: "audio/ogg",
|
|
1052
|
+
file_size: 42,
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
1059
|
+
if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
|
|
1060
|
+
throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return JSON.stringify({
|
|
1064
|
+
event_id: `zap_evt_audio_${Date.now()}`,
|
|
1065
|
+
instance_id: "local",
|
|
1066
|
+
message: {
|
|
1067
|
+
id: `wamid.audio.${Date.now()}`,
|
|
1068
|
+
from: "5511999999999",
|
|
1069
|
+
type: "audio",
|
|
1070
|
+
audio: {
|
|
1071
|
+
id: `media_audio_${Date.now()}`,
|
|
1072
|
+
url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
|
|
1073
|
+
mime_type: "audio/ogg",
|
|
1074
|
+
duration: 12,
|
|
1075
|
+
file_size: 42,
|
|
1076
|
+
...(fixture === "voice-note" ? { ptt: "true" } : {}),
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function isExplicitFixturePath(value: string): boolean {
|
|
1086
|
+
return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
|
|
1087
|
+
}
|
|
1088
|
+
|
|
977
1089
|
function channelTestHeaders(channel: CloudChannel): Headers {
|
|
978
1090
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
979
1091
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { TranscriptionProviderName } from "../../index";
|
|
5
|
+
import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
|
|
6
|
+
import { loadCapsuleEnv } from "../../runtime/env";
|
|
7
|
+
import {
|
|
8
|
+
defaultTranscriptionSecret,
|
|
9
|
+
resolveTranscriptionConfig,
|
|
10
|
+
transcribeAudio,
|
|
11
|
+
type ResolvedTranscriptionConfig,
|
|
12
|
+
} from "../../runtime/transcription";
|
|
13
|
+
import type { ParsedArgs } from "../args";
|
|
14
|
+
|
|
15
|
+
type SmokeFixture = {
|
|
16
|
+
audio: Uint8Array;
|
|
17
|
+
filename: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
source: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const defaultModels: Record<TranscriptionProviderName, string> = {
|
|
23
|
+
test: "fake",
|
|
24
|
+
openai: "gpt-4o-mini-transcribe",
|
|
25
|
+
groq: "whisper-large-v3-turbo",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Short macOS-generated spoken "hello" sample encoded as M4A. M4A is supported by OpenAI and Groq.
|
|
29
|
+
const builtinSmokeM4aBase64 =
|
|
30
|
+
"AAAAHGZ0eXBNNEEgAAAAAE00QSBtcDQyaXNvbQAAA3ptb292AAAAbG12aGQAAAAA5kTZseZE2bEAAFYiAAA4AAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACDHRyYWsAAABcdGtoZAAAAAfmRNmx5kTZsQAAAAEAAAAAAAA4AAAAAAAAAAAAAAAAAAEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAahtZGlhAAAAIG1kaGQAAAAA5kTZseZE2bEAAFYiAAA4AAAAAAAAAAAiaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAAAAAAABXm1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABInN0YmwAAAB2c3RzZAAAAAAAAAABAAAAZm1wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAABWIgAAAAAAM2VzZHMAAAAAA4CAgCIAAAAEgICAFEAUABgAAAAU+AAAfQAFgICAAhOIBoCAgAECAAAAD3NidGQAAAAASTE2AAAAGHN0dHMAAAAAAAAAAQAAAA4AAAQAAAAAKHN0c2MAAAAAAAAAAgAAAAEAAAALAAAAAQAAAAIAAAADAAAAAQAAAExzdHN6AAAAAAAAAAAAAAAOAAAABAAAAbkAAADiAAAA0wAAAL8AAADLAAAAzAAAAJMAAACmAAAA5wAAALcAAACnAAAAtgAAADoAAAAYc3RjbwAAAAAAAAACAAAQAAAAGJ8AAAD6dWR0YQAAAPJtZXRhAAAAAAAAACJoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAADEaWxzdAAAALwtLS0tAAAAHG1lYW4AAAAAY29tLmFwcGxlLmlUdW5lcwAAABRuYW1lAAAAAGlUdW5TTVBCAAAAhGRhdGEAAAABAAAAACAwMDAwMDAwMCAwMDAwMDg0MCAwMDAwMDEwOCAwMDAwMDAwMDAwMDAyRUI4IDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwAAAMYmZyZWUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo+bWRhdADQAAcBIFeYt4YVhoiCYSBAIiAb/R5dZpElTUczwuqGbayKzLVhbbKk0g4RDwEANLB+jRqx7w7T4vyuKXjECir/JjyEyeQp2CFaQRCEnEh2lAIrlk8/qMCw1p6gnzvtRPv3gkn2Pm5LF7Qh1fvxDs/Bied7IT7jxcnT15Pwt6WJ/Gf1yS4J3Qh539FkPHWsJ+BPVhP2T6UJ+cvUZP4IfHUn8qPlAT+OPw5J/Aj2ST8lZMhxrs5DsfKiGqxZDE4Ei60T0+2J67PE8tm8p5rW1fRH1CrbiIAByLkIhCI/t26HXZA+95UN7hJhCD4RB7CCi9A5jptvSBE5K9Y5sVYR3BVChFIXuPte8103zGibvb/4MmNRNzLvL2X3H3m/ppr/Z8xp2ucdI29FKeAVRCL5T/r6rmsQtjcFIO7IAPpkDp/zvU+kHm51ysNIya3CMHAAGCERAWZY7ctnZ48Yzna6MAkKw7TPmx6+JU+tx1YN7BoAbxOKEXiMeMQsYOGZaJ8AMU/j7bwRx20qKSvG4e3GU8GPfsCEs7OgoqwebxGSWZiea9kq5ii5qqgpB9QQPDw93dx6ywAftSCS1BUFQ4AA6J+u0sSHANlmmNoQNpORaEC/+v5Wzp1UfX3/4/QJnt0vr+9+tawdL6g25kAnxnQ3CJrpg05ZjGuu6nm9UNvaqYlyaHGtH+osWdTe2Cvfc1n9gsFkIyumPBGEFzTvTZwc8AiNfWN1no++Po/zGN8+nkMNjiGOit4pglz65wnS9Rjh3werBQQtzby3QZBjWV2er23+u8bwoDlA/hTH398+fpIBVGRqfXy2NUXrKjSUAHRcwAs56XW3C6e1Yeoj5zimM/7jm3lf44hpnU0hYDf/uM8Yf6kVAXdxJroeLjVwJyYcAUTXiFSyDY2SAUKAUEJACY0CIQEM90avOuV4ogSxnVOjyUA9xWrzcbbtFiVGggjwq5sMoI84IRPP+bW+lz8YB254Z/K4RRSkbW78T91AACiHZgbHEGOTAAiWU3jabICr2d3hgrMCBdnPglJvhMFPbMlxXUDEslEMva9v0s7MpHI8mofD65pP23QgAz7TN742tW4d5xjKWrg8uzanDywnK1rxmgBTg1etyW7Tst7o/KdrmlpBagAzdlmz9U2Ja33diaxiuGm50Tb8xApFx3jZ1rWoBwFCF6YQEAsUSIUQoIREIAiEAiFAiEBG/HNOnbqNkaDp5VuRV6mnW8GLNkjlfn/RgvoPzvhgy7HxvFyGO7pq1pBGhjFHN21iABGGvGZAAGLDDAQU9fkDIBxoKp0QUFO4KC0LYgoKrFQVDsWHTSK8lwx50sqB37FF4OtQXW26nbRcsXEwVu5EkFIjnhU0Gsj/xnyZvTmaA9xDC0c+BlxAAHz0/L//+Pbo4C8f8WoIYY8kXYWBvw8ykc2yn5wVhMHAATwXpowhQwkKAhCgQCoUCIQE2/KnTlDGLtw6x9xmw68tdhtyVBf0P7NIYZ/c/CacDU0MPnmdLriY7uBmGGGpmE4akeT0CGzz6ZIJQzn+P8AP6ffeS0wf47f22n5RAEQblPdPv6j7LYuyy757/G3b/h4eIeCyXAQBbR9E2hB/4fjuOAGH7HR9LsMaBkRO8czZc4PXaXWALevWZMov2QbCReA9ezcAAavU0qZ6sfd9Hl4NdzpL82Opao6HaFMejXzzuXfytDl1w/OMYAcBPBegoigQigrGJDCQgjAahQIhATHzhfZo5WRc6nHAehOpf48UF03PraBs/ALgQ2E0OAnl67g4pIgS+7+DUzSXKAAIkEEiGkpxpmnbx35+ak6b3Rz8WUbkGxC+3AWmiZqcrj8msbvZ5pUE7lhrSO7LPL9b8KIqwnGi0DkKjkzSdkJlT7qY04DdAw4tUvHE7xaKdCCvla76v4YqoptXwfPRjuWrHtIAABi7ZjFZ7uzPf18N92vn9f6Kl8KCitAKWNiW50a8NbYZJFFHIcABQheIVkhKIAqDUSCAIjAIhAYhATHPbrwMUDb6f4eIV7HnoLjHcm8LFCsgAN9nXMuc4DEIDuWm3QbqWSGGIYDkw8DoXJUXb1paGH4sznSxoYXML4WeOBZjXbyiWNNAXZ7sYngwGBJUEYxNW057OO8qS9XBpKa8dzch5CV5sX2i9PgxM2r/9a1twBnCAABhwt1048ABRheIdEQkDRQDQbCELBMQBQQiAQiAR4i+wvwQZxry/V3RrHsa+sCYdn5d0ecHQQlamg/U1LQyNKYJgIVEdkyW0MVPmcw+6D35uzUHM5V0WGOSsMWpsZr3ljM74VFXganqittY0rej1EouBgYPN77qjrMx9307eHSbv/rKbU7k4ABKcJw6nNuppqXkPNy54HVctDG7bYQAqtG1JWuEQvnAAAqxv2T4AU4XgFR2TRRKhBDQjFQhEAhEAm14UMD+V6Vz7UtuXflxxW9+2B4/6LkfE6a8aVBIliFAhZXzBPfq813CXKyMK1dnqeBi2DLIFeEpxyldkWW/XLJWu0trEfPK3AyoA1o8OIJ1AD41cMgqpP9lDH+2/Tp+e9C375rdsBXCWvwOVhYxpMZcSojQ9BYkOcztLjw0qx8cwLUgQI/tdHlaIA3WX6L97fKmVPBM1qD0rwyVGodixekZUVxOaWKe4KtbFrUFhc2uQ6kYIWI48y6NDFeT+NpQAsu/jkw0/jEIE6RAnVQAtR20Trt4AUwXpdRxGBGIAaEAxOAhm4MtT+VC+f3ddnXF6DYD0z8lHUfDSa+wX00nH9ZxNTblKT5C8rUzH8KvP0nx/olydrMwwdhcFTYJUUiAiSPNhh43UARxtkmOAYEqiyai1jTGPJelEHEim5C4sgAeI5SFyxHqrusVKS14izaVjkukv1m3ZWRfqQUYrtv8q2B+OBWcU5zobcMJyy3XjXUNeCbU4E09gq/LmrdPkKUS2r1oqmAI6suWnaPAAT4XoiQgQYmEIoCwQEJTGAjthBTgZ1/7Vscb82UyBwRHuv2vhg8r3nnLZmic4VKE6NTNU5SU4YFokmFAk0MmXkzXBByh2PMXMX/lI74PWvmoeWGvx/wKUwA/Xq+kgCQTOlToWZTRkeFulKz4WeEYSACE7uHchSdipqydhQlBaXoVbbYLYylNDlySRxMiCZZl4dCUqdR9G9Ql9W/8f+/cjrOAAATAAcABPheliFNKFETBMgkYIhAYNuugi20/Dg0ibX126Yh2Bfbf2LwUL6HpRfddGr1vHeak/BNP8KGjfgsOq9JM0RiN6xfevi5udNmUc3wuzNBX8qyNyOm3fN4qwXob4LcChpMFq+ZcBEZ5GMyPwQ5Mfzw6v6Lm7qdwCyMcHHYjK5THW9PS+12T29NbdsrphS8YhU/IpxOP5XiVla5fdRgcYgKGAGJnRgYWIeLQWFl3a4PTXKmOkhrAHAEgF5lIaCBt9xvXAaY6O1tqlQyD/EfAEVTYX3SzOcDM/XPVM5KsEwq+9RWLc7MjGS4W62zZNKcIQhc=";
|
|
31
|
+
|
|
32
|
+
export async function handleTranscribeCommand(args: ParsedArgs): Promise<void> {
|
|
33
|
+
const [subcommand] = args.positional;
|
|
34
|
+
|
|
35
|
+
if (subcommand !== "smoke") {
|
|
36
|
+
throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const root = await findAgentCapsuleRoot(process.cwd());
|
|
40
|
+
const capsule = await loadAgentCapsule(root);
|
|
41
|
+
const env = compactEnv(await loadCapsuleEnv(root, process.env));
|
|
42
|
+
const configured = resolveTranscriptionConfig(capsule.config.transcription);
|
|
43
|
+
const provider = parseProviderFlag(args.flags.provider, configured);
|
|
44
|
+
const model = typeof args.flags.model === "string" ? args.flags.model : configured.enabled && configured.provider === provider ? configured.model : defaultModels[provider];
|
|
45
|
+
const secret = typeof args.flags.secret === "string"
|
|
46
|
+
? args.flags.secret
|
|
47
|
+
: configured.enabled && configured.provider === provider
|
|
48
|
+
? configured.secret
|
|
49
|
+
: defaultTranscriptionSecret(provider);
|
|
50
|
+
const fixture = await loadSmokeFixture(provider, args.flags.fixture);
|
|
51
|
+
|
|
52
|
+
const config: ResolvedTranscriptionConfig = {
|
|
53
|
+
enabled: true,
|
|
54
|
+
provider,
|
|
55
|
+
model,
|
|
56
|
+
secret,
|
|
57
|
+
language: typeof args.flags.language === "string" ? args.flags.language : configured.enabled ? configured.language : null,
|
|
58
|
+
prompt: typeof args.flags.prompt === "string" ? args.flags.prompt : configured.enabled ? configured.prompt : null,
|
|
59
|
+
limits: configured.enabled ? configured.limits : {},
|
|
60
|
+
rawAudioTtlSeconds: configured.enabled ? configured.rawAudioTtlSeconds : 3600,
|
|
61
|
+
};
|
|
62
|
+
const result = await transcribeAudio({
|
|
63
|
+
config,
|
|
64
|
+
secrets: env,
|
|
65
|
+
audio: fixture.audio,
|
|
66
|
+
filename: fixture.filename,
|
|
67
|
+
mimeType: fixture.mimeType,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log("Transcription smoke");
|
|
71
|
+
console.log(`Provider: ${provider}`);
|
|
72
|
+
console.log(`Model: ${model}`);
|
|
73
|
+
console.log(`Fixture: ${fixture.source} (${fixture.mimeType})`);
|
|
74
|
+
|
|
75
|
+
if (result.ok) {
|
|
76
|
+
console.log("Status: ok");
|
|
77
|
+
console.log(`Transcript: ${JSON.stringify(result.text)}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log("Status: failed");
|
|
82
|
+
console.log(`Code: ${result.code}`);
|
|
83
|
+
console.log(`Message: ${result.message}`);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseProviderFlag(
|
|
88
|
+
value: string | boolean | undefined,
|
|
89
|
+
configured: ResolvedTranscriptionConfig,
|
|
90
|
+
): TranscriptionProviderName {
|
|
91
|
+
if (value === undefined || value === false) {
|
|
92
|
+
return configured.enabled ? configured.provider : "groq";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (value === "test" || value === "openai" || value === "groq") {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function compactEnv(env: Record<string, string | undefined>): Record<string, string> {
|
|
103
|
+
const compact: Record<string, string> = {};
|
|
104
|
+
|
|
105
|
+
for (const [name, value] of Object.entries(env)) {
|
|
106
|
+
if (value !== undefined) {
|
|
107
|
+
compact[name] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return compact;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function loadSmokeFixture(
|
|
115
|
+
provider: TranscriptionProviderName,
|
|
116
|
+
fixtureFlag: string | boolean | undefined,
|
|
117
|
+
): Promise<SmokeFixture> {
|
|
118
|
+
if (typeof fixtureFlag === "string") {
|
|
119
|
+
const filename = resolve(process.cwd(), fixtureFlag);
|
|
120
|
+
return {
|
|
121
|
+
audio: new Uint8Array(await readFile(filename)),
|
|
122
|
+
filename: basename(filename),
|
|
123
|
+
mimeType: mimeTypeForFilename(filename),
|
|
124
|
+
source: fixtureFlag,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (provider === "test") {
|
|
129
|
+
return {
|
|
130
|
+
audio: new TextEncoder().encode("hello"),
|
|
131
|
+
filename: "agentkit-smoke.ogg",
|
|
132
|
+
mimeType: "audio/ogg",
|
|
133
|
+
source: "built-in text fixture",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
audio: new Uint8Array(Buffer.from(builtinSmokeM4aBase64, "base64")),
|
|
139
|
+
filename: "agentkit-transcribe-smoke.m4a",
|
|
140
|
+
mimeType: "audio/m4a",
|
|
141
|
+
source: "built-in spoken hello",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function mimeTypeForFilename(filename: string): string {
|
|
146
|
+
const extension = extname(filename).toLowerCase();
|
|
147
|
+
|
|
148
|
+
if (extension === ".ogg" || extension === ".opus") {
|
|
149
|
+
return "audio/ogg";
|
|
150
|
+
}
|
|
151
|
+
if (extension === ".mp3" || extension === ".mpeg" || extension === ".mpga") {
|
|
152
|
+
return "audio/mpeg";
|
|
153
|
+
}
|
|
154
|
+
if (extension === ".m4a") {
|
|
155
|
+
return "audio/m4a";
|
|
156
|
+
}
|
|
157
|
+
if (extension === ".mp4") {
|
|
158
|
+
return "audio/mp4";
|
|
159
|
+
}
|
|
160
|
+
if (extension === ".wav") {
|
|
161
|
+
return "audio/wav";
|
|
162
|
+
}
|
|
163
|
+
if (extension === ".webm") {
|
|
164
|
+
return "audio/webm";
|
|
165
|
+
}
|
|
166
|
+
if (extension === ".flac") {
|
|
167
|
+
return "audio/flac";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return "application/octet-stream";
|
|
171
|
+
}
|
|
@@ -70,6 +70,27 @@ export async function startDeployChatUiServer(
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
if (
|
|
74
|
+
request.method === "GET" &&
|
|
75
|
+
(requestUrl.pathname === "/v1/conversations" ||
|
|
76
|
+
/^\/v1\/conversations\/[^/]+(?:\/trace)?$/.test(requestUrl.pathname))
|
|
77
|
+
) {
|
|
78
|
+
const token = await readDeployAccessTokenForUi(tokenPath);
|
|
79
|
+
const upstream = await fetch(new URL(requestUrl.pathname, deployUrl).href, {
|
|
80
|
+
method: "GET",
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: `Bearer ${token}`,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const text = await upstream.text();
|
|
86
|
+
|
|
87
|
+
response.writeHead(upstream.status, {
|
|
88
|
+
"content-type": upstream.headers.get("content-type") ?? "application/json",
|
|
89
|
+
});
|
|
90
|
+
response.end(text);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
sendJson(response, 404, { error: { code: "not_found", message: "Route not found." } });
|
|
74
95
|
} catch (error) {
|
|
75
96
|
sendJson(response, 500, {
|
|
@@ -196,6 +217,8 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
196
217
|
header {
|
|
197
218
|
border-bottom: 1px solid var(--line);
|
|
198
219
|
padding: 18px 22px;
|
|
220
|
+
display: grid;
|
|
221
|
+
gap: 10px;
|
|
199
222
|
}
|
|
200
223
|
|
|
201
224
|
h1 {
|
|
@@ -204,12 +227,27 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
204
227
|
line-height: 1.2;
|
|
205
228
|
}
|
|
206
229
|
|
|
230
|
+
.topline {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: start;
|
|
233
|
+
justify-content: space-between;
|
|
234
|
+
gap: 12px;
|
|
235
|
+
}
|
|
236
|
+
|
|
207
237
|
code {
|
|
208
238
|
color: var(--muted);
|
|
209
239
|
font-size: 12px;
|
|
210
240
|
overflow-wrap: anywhere;
|
|
211
241
|
}
|
|
212
242
|
|
|
243
|
+
.meta {
|
|
244
|
+
display: flex;
|
|
245
|
+
flex-wrap: wrap;
|
|
246
|
+
gap: 8px 14px;
|
|
247
|
+
color: var(--muted);
|
|
248
|
+
font-size: 12px;
|
|
249
|
+
}
|
|
250
|
+
|
|
213
251
|
#messages {
|
|
214
252
|
padding: 22px;
|
|
215
253
|
overflow: auto;
|
|
@@ -255,6 +293,39 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
255
293
|
background: #fff6f6;
|
|
256
294
|
}
|
|
257
295
|
|
|
296
|
+
#trace {
|
|
297
|
+
border-top: 1px solid var(--line);
|
|
298
|
+
padding: 12px 22px;
|
|
299
|
+
display: grid;
|
|
300
|
+
gap: 8px;
|
|
301
|
+
background: #fbfbfa;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.tool-call {
|
|
305
|
+
border: 1px solid var(--line);
|
|
306
|
+
border-radius: 8px;
|
|
307
|
+
padding: 10px 12px;
|
|
308
|
+
display: grid;
|
|
309
|
+
gap: 6px;
|
|
310
|
+
font-size: 12px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.tool-head {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-wrap: wrap;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
align-items: center;
|
|
318
|
+
color: var(--muted);
|
|
319
|
+
font-weight: 700;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.tool-call pre {
|
|
323
|
+
margin: 0;
|
|
324
|
+
max-height: 180px;
|
|
325
|
+
overflow: auto;
|
|
326
|
+
white-space: pre-wrap;
|
|
327
|
+
}
|
|
328
|
+
|
|
258
329
|
form {
|
|
259
330
|
border-top: 1px solid var(--line);
|
|
260
331
|
padding: 14px;
|
|
@@ -286,6 +357,13 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
286
357
|
cursor: pointer;
|
|
287
358
|
}
|
|
288
359
|
|
|
360
|
+
button.secondary {
|
|
361
|
+
min-height: 34px;
|
|
362
|
+
color: var(--ink);
|
|
363
|
+
background: #eef1ee;
|
|
364
|
+
border: 1px solid var(--line);
|
|
365
|
+
}
|
|
366
|
+
|
|
289
367
|
button:disabled {
|
|
290
368
|
cursor: wait;
|
|
291
369
|
opacity: 0.65;
|
|
@@ -305,10 +383,20 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
305
383
|
<body>
|
|
306
384
|
<main>
|
|
307
385
|
<header>
|
|
308
|
-
<
|
|
309
|
-
|
|
386
|
+
<div class="topline">
|
|
387
|
+
<div>
|
|
388
|
+
<h1>AgentKit Hosted Chat</h1>
|
|
389
|
+
<code>${deployUrl}</code>
|
|
390
|
+
</div>
|
|
391
|
+
<button class="secondary" id="new-conversation" type="button">New conversation</button>
|
|
392
|
+
</div>
|
|
393
|
+
<div class="meta">
|
|
394
|
+
<span>Conversation: <code id="conversation-id">none</code></span>
|
|
395
|
+
<span>Tool calls: <code id="tool-count">0</code></span>
|
|
396
|
+
</div>
|
|
310
397
|
</header>
|
|
311
398
|
<section id="messages" aria-live="polite"></section>
|
|
399
|
+
<section id="trace" aria-live="polite"></section>
|
|
312
400
|
<form id="form">
|
|
313
401
|
<textarea id="message" name="message" placeholder="Type a message" required autofocus></textarea>
|
|
314
402
|
<button id="send" type="submit">Send</button>
|
|
@@ -319,6 +407,10 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
319
407
|
const textarea = document.querySelector("#message");
|
|
320
408
|
const send = document.querySelector("#send");
|
|
321
409
|
const messages = document.querySelector("#messages");
|
|
410
|
+
const trace = document.querySelector("#trace");
|
|
411
|
+
const conversationLabel = document.querySelector("#conversation-id");
|
|
412
|
+
const toolCount = document.querySelector("#tool-count");
|
|
413
|
+
const newConversation = document.querySelector("#new-conversation");
|
|
322
414
|
let conversationId = null;
|
|
323
415
|
|
|
324
416
|
function addMessage(role, content, className = "") {
|
|
@@ -335,6 +427,55 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
335
427
|
messages.scrollTop = messages.scrollHeight;
|
|
336
428
|
}
|
|
337
429
|
|
|
430
|
+
function setConversationId(value) {
|
|
431
|
+
conversationId = value || null;
|
|
432
|
+
conversationLabel.textContent = conversationId || "none";
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderTrace(payload) {
|
|
436
|
+
const calls = [];
|
|
437
|
+
for (const run of payload.runs || []) {
|
|
438
|
+
for (const call of run.toolCalls || []) {
|
|
439
|
+
calls.push(call);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
toolCount.textContent = String(calls.length);
|
|
444
|
+
trace.innerHTML = "";
|
|
445
|
+
|
|
446
|
+
for (const call of calls.slice(-8)) {
|
|
447
|
+
const item = document.createElement("article");
|
|
448
|
+
item.className = "tool-call";
|
|
449
|
+
const head = document.createElement("div");
|
|
450
|
+
head.className = "tool-head";
|
|
451
|
+
head.textContent = [call.name, call.status, call.errorMessage].filter(Boolean).join(" · ");
|
|
452
|
+
const input = document.createElement("pre");
|
|
453
|
+
input.textContent = JSON.stringify(call.input || {}, null, 2);
|
|
454
|
+
item.append(head, input);
|
|
455
|
+
trace.append(item);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function refreshTrace() {
|
|
460
|
+
if (!conversationId) {
|
|
461
|
+
toolCount.textContent = "0";
|
|
462
|
+
trace.innerHTML = "";
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const response = await fetch("/v1/conversations/" + encodeURIComponent(conversationId) + "/trace");
|
|
467
|
+
const payload = await response.json();
|
|
468
|
+
if (response.ok) renderTrace(payload);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
newConversation.addEventListener("click", () => {
|
|
472
|
+
setConversationId(null);
|
|
473
|
+
messages.innerHTML = "";
|
|
474
|
+
trace.innerHTML = "";
|
|
475
|
+
toolCount.textContent = "0";
|
|
476
|
+
textarea.focus();
|
|
477
|
+
});
|
|
478
|
+
|
|
338
479
|
textarea.addEventListener("keydown", (event) => {
|
|
339
480
|
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
|
340
481
|
event.preventDefault();
|
|
@@ -363,10 +504,12 @@ function renderDeployChatUi(input: { deployUrl: string }): string {
|
|
|
363
504
|
throw new Error(payload.error?.message || "Request failed");
|
|
364
505
|
}
|
|
365
506
|
|
|
366
|
-
|
|
507
|
+
setConversationId(payload.conversationId || conversationId);
|
|
367
508
|
addMessage("assistant", payload.message?.content || "");
|
|
509
|
+
await refreshTrace();
|
|
368
510
|
} catch (error) {
|
|
369
511
|
addMessage("error", error instanceof Error ? error.message : String(error), "error");
|
|
512
|
+
await refreshTrace().catch(() => {});
|
|
370
513
|
} finally {
|
|
371
514
|
send.disabled = false;
|
|
372
515
|
textarea.focus();
|