@axlsdk/studio 0.21.1 → 0.22.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/README.md +1 -1
- package/dist/{chunk-YGLWYDQ3.js → chunk-Z374DI7D.js} +226 -11
- package/dist/chunk-Z374DI7D.js.map +1 -0
- package/dist/cli.cjs +226 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/index-7nGvwNtb.js +318 -0
- package/dist/client/assets/index-Bc4iQdXy.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/middleware.cjs +225 -10
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/server/index.cjs +225 -10
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-YGLWYDQ3.js.map +0 -1
- package/dist/client/assets/index-C8R_CZcW.css +0 -1
- package/dist/client/assets/index-Dq8QQtXe.js +0 -313
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Or run directly with npx (no install needed):
|
|
|
20
20
|
npx @axlsdk/studio
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**Requirements:** Node.js
|
|
23
|
+
**Requirements:** Node.js 22+, an existing Axl project with `@axlsdk/axl` installed.
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
@@ -89,6 +89,19 @@ function redactStreamEvent(event, redact) {
|
|
|
89
89
|
if (!redact) return event;
|
|
90
90
|
return redactHistoricalEvent(event);
|
|
91
91
|
}
|
|
92
|
+
function sanitizeRichInputFailure(event) {
|
|
93
|
+
const message = "Playground media input failed";
|
|
94
|
+
if (event.type === "ask_end" && !event.outcome.ok) {
|
|
95
|
+
return { ...event, outcome: { ok: false, error: message } };
|
|
96
|
+
}
|
|
97
|
+
if (event.type === "agent_call_end" && event.data.error !== void 0) {
|
|
98
|
+
return { ...event, data: { ...event.data, error: message } };
|
|
99
|
+
}
|
|
100
|
+
if (event.type === "error") {
|
|
101
|
+
return { ...event, data: { ...event.data, message } };
|
|
102
|
+
}
|
|
103
|
+
return event;
|
|
104
|
+
}
|
|
92
105
|
function redactEvalItem(item) {
|
|
93
106
|
const scrubbed = {
|
|
94
107
|
...item,
|
|
@@ -807,11 +820,11 @@ function reduceCost(acc, event) {
|
|
|
807
820
|
byWorkflow2[event.workflow] = { ...prev, executions: prev.executions + 1 };
|
|
808
821
|
return { ...acc, byWorkflow: byWorkflow2 };
|
|
809
822
|
}
|
|
810
|
-
|
|
823
|
+
const unpricedLeaf = isUnpricedLeaf(event);
|
|
824
|
+
if (event.cost == null && !event.tokens && !unpricedLeaf) return acc;
|
|
811
825
|
const cost = eventCostContribution(event);
|
|
812
826
|
if (event.type === "ask_end") return acc;
|
|
813
827
|
const tokens = event.tokens ?? {};
|
|
814
|
-
const unpricedLeaf = isUnpricedLeaf(event);
|
|
815
828
|
const totalTokens = event.type === "agent_call_end" ? {
|
|
816
829
|
input: acc.totalTokens.input + finite(tokens.input),
|
|
817
830
|
output: acc.totalTokens.output + finite(tokens.output),
|
|
@@ -1324,6 +1337,24 @@ var executions_default = createExecutionRoutes();
|
|
|
1324
1337
|
|
|
1325
1338
|
// src/server/routes/sessions.ts
|
|
1326
1339
|
import { Hono as Hono4 } from "hono";
|
|
1340
|
+
function describeStudioInput(content) {
|
|
1341
|
+
if (typeof content === "string") return void 0;
|
|
1342
|
+
return {
|
|
1343
|
+
parts: content.map((part) => {
|
|
1344
|
+
if (part.type === "text") return { type: "text", characters: part.text.length };
|
|
1345
|
+
const { source } = part;
|
|
1346
|
+
const bytes = source.type === "bytes" ? source.data.byteLength : source.type === "base64" ? Math.floor(source.data.length * 3 / 4) - (source.data.endsWith("==") ? 2 : source.data.endsWith("=") ? 1 : 0) : void 0;
|
|
1347
|
+
return {
|
|
1348
|
+
type: "image",
|
|
1349
|
+
source: source.type,
|
|
1350
|
+
...source.mediaType ? { mediaType: source.mediaType } : {},
|
|
1351
|
+
...bytes !== void 0 ? { bytes } : {}
|
|
1352
|
+
// URLs, provider-file references, and labels are intentionally not
|
|
1353
|
+
// included: this is a Studio-safe descriptor, not a replay payload.
|
|
1354
|
+
};
|
|
1355
|
+
})
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1327
1358
|
function createSessionRoutes(connMgr) {
|
|
1328
1359
|
const app5 = new Hono4();
|
|
1329
1360
|
app5.get("/sessions", async (c) => {
|
|
@@ -1346,11 +1377,17 @@ function createSessionRoutes(connMgr) {
|
|
|
1346
1377
|
const id = c.req.param("id");
|
|
1347
1378
|
const history = await store.getSession(id);
|
|
1348
1379
|
const handoffHistory = await store.getSessionMeta(id, "handoffHistory");
|
|
1380
|
+
const redact = runtime.isRedactEnabled();
|
|
1381
|
+
const studioHistory = history.map((message) => {
|
|
1382
|
+
const input = describeStudioInput(message.content);
|
|
1383
|
+
if (input) return { ...message, content: input };
|
|
1384
|
+
return redact ? redactSessionHistory([message], true)[0] : message;
|
|
1385
|
+
});
|
|
1349
1386
|
return c.json({
|
|
1350
1387
|
ok: true,
|
|
1351
1388
|
data: {
|
|
1352
1389
|
id,
|
|
1353
|
-
history:
|
|
1390
|
+
history: studioHistory,
|
|
1354
1391
|
// HandoffRecord has no content fields (source/target/mode/
|
|
1355
1392
|
// timestamp/duration) — nothing to scrub.
|
|
1356
1393
|
handoffHistory: handoffHistory ?? []
|
|
@@ -15920,6 +15957,54 @@ function date4(params) {
|
|
|
15920
15957
|
// ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
15921
15958
|
config(en_default());
|
|
15922
15959
|
|
|
15960
|
+
// src/playground-image.ts
|
|
15961
|
+
var PLAYGROUND_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
15962
|
+
var PLAYGROUND_IMAGE_MAX_BASE64_CHARACTERS = Math.ceil(PLAYGROUND_IMAGE_MAX_BYTES / 3) * 4;
|
|
15963
|
+
var PLAYGROUND_IMAGE_REQUEST_MAX_BYTES = PLAYGROUND_IMAGE_MAX_BASE64_CHARACTERS + 16 * 1024;
|
|
15964
|
+
var PLAYGROUND_IMAGE_MEDIA_TYPES = [
|
|
15965
|
+
"image/png",
|
|
15966
|
+
"image/jpeg",
|
|
15967
|
+
"image/webp",
|
|
15968
|
+
"image/gif"
|
|
15969
|
+
];
|
|
15970
|
+
function isPlaygroundImageMediaType(value) {
|
|
15971
|
+
return typeof value === "string" && PLAYGROUND_IMAGE_MEDIA_TYPES.includes(value);
|
|
15972
|
+
}
|
|
15973
|
+
function base64ByteLength(value) {
|
|
15974
|
+
if (value.length === 0 || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
|
|
15975
|
+
return void 0;
|
|
15976
|
+
}
|
|
15977
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
15978
|
+
return value.length / 4 * 3 - padding;
|
|
15979
|
+
}
|
|
15980
|
+
function decodedPrefix(value, length) {
|
|
15981
|
+
try {
|
|
15982
|
+
const prefix = value.slice(0, Math.ceil(length / 3) * 4);
|
|
15983
|
+
const decoded = atob(prefix);
|
|
15984
|
+
return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
|
|
15985
|
+
} catch {
|
|
15986
|
+
return void 0;
|
|
15987
|
+
}
|
|
15988
|
+
}
|
|
15989
|
+
function hasMatchingImageSignature(mediaType, data) {
|
|
15990
|
+
const bytes = decodedPrefix(data, 12);
|
|
15991
|
+
if (!bytes) return false;
|
|
15992
|
+
const startsWith = (...prefix) => prefix.every((byte, index) => bytes[index] === byte);
|
|
15993
|
+
switch (mediaType) {
|
|
15994
|
+
case "image/png":
|
|
15995
|
+
return startsWith(137, 80, 78, 71, 13, 10, 26, 10);
|
|
15996
|
+
case "image/jpeg":
|
|
15997
|
+
return startsWith(255, 216, 255);
|
|
15998
|
+
case "image/gif":
|
|
15999
|
+
return startsWith(71, 73, 70, 56) && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97;
|
|
16000
|
+
case "image/webp":
|
|
16001
|
+
return startsWith(82, 73, 70, 70) && startsWithAt(bytes, 8, 87, 69, 66, 80);
|
|
16002
|
+
}
|
|
16003
|
+
}
|
|
16004
|
+
function startsWithAt(bytes, offset, ...prefix) {
|
|
16005
|
+
return prefix.every((byte, index) => bytes[offset + index] === byte);
|
|
16006
|
+
}
|
|
16007
|
+
|
|
15923
16008
|
// src/server/routes/playground.ts
|
|
15924
16009
|
var DEMO_SCHEMA_BY_AGENT = {
|
|
15925
16010
|
"streaming-structured-agent": external_exports.object({
|
|
@@ -15929,11 +16014,74 @@ var DEMO_SCHEMA_BY_AGENT = {
|
|
|
15929
16014
|
confidence: external_exports.number().min(0).max(1)
|
|
15930
16015
|
})
|
|
15931
16016
|
};
|
|
16017
|
+
var PlaygroundBodyTooLargeError = class extends Error {
|
|
16018
|
+
};
|
|
16019
|
+
async function readPlaygroundBody(request) {
|
|
16020
|
+
const contentLength = request.headers.get("content-length");
|
|
16021
|
+
if (contentLength !== null) {
|
|
16022
|
+
const bytes = Number(contentLength);
|
|
16023
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > PLAYGROUND_IMAGE_REQUEST_MAX_BYTES) {
|
|
16024
|
+
throw new PlaygroundBodyTooLargeError();
|
|
16025
|
+
}
|
|
16026
|
+
}
|
|
16027
|
+
if (!request.body) throw new SyntaxError("missing body");
|
|
16028
|
+
const reader = request.body.getReader();
|
|
16029
|
+
const chunks = [];
|
|
16030
|
+
let total = 0;
|
|
16031
|
+
try {
|
|
16032
|
+
while (true) {
|
|
16033
|
+
const next = await reader.read();
|
|
16034
|
+
if (next.done) break;
|
|
16035
|
+
total += next.value.byteLength;
|
|
16036
|
+
if (total > PLAYGROUND_IMAGE_REQUEST_MAX_BYTES) {
|
|
16037
|
+
await reader.cancel();
|
|
16038
|
+
throw new PlaygroundBodyTooLargeError();
|
|
16039
|
+
}
|
|
16040
|
+
chunks.push(next.value);
|
|
16041
|
+
}
|
|
16042
|
+
} finally {
|
|
16043
|
+
reader.releaseLock();
|
|
16044
|
+
}
|
|
16045
|
+
const body = new Uint8Array(total);
|
|
16046
|
+
let offset = 0;
|
|
16047
|
+
for (const chunk of chunks) {
|
|
16048
|
+
body.set(chunk, offset);
|
|
16049
|
+
offset += chunk.byteLength;
|
|
16050
|
+
}
|
|
16051
|
+
return JSON.parse(new TextDecoder().decode(body));
|
|
16052
|
+
}
|
|
15932
16053
|
function createPlaygroundRoutes(connMgr) {
|
|
15933
16054
|
const app5 = new Hono11();
|
|
15934
16055
|
app5.post("/playground/chat", async (c) => {
|
|
15935
16056
|
const runtime = c.get("runtime");
|
|
15936
|
-
|
|
16057
|
+
let parsedBody;
|
|
16058
|
+
try {
|
|
16059
|
+
parsedBody = await readPlaygroundBody(c.req.raw);
|
|
16060
|
+
} catch (error48) {
|
|
16061
|
+
if (error48 instanceof PlaygroundBodyTooLargeError) {
|
|
16062
|
+
return c.json(
|
|
16063
|
+
{
|
|
16064
|
+
ok: false,
|
|
16065
|
+
error: { code: "REQUEST_TOO_LARGE", message: "playground image request is too large" }
|
|
16066
|
+
},
|
|
16067
|
+
413
|
|
16068
|
+
);
|
|
16069
|
+
}
|
|
16070
|
+
return c.json(
|
|
16071
|
+
{ ok: false, error: { code: "INVALID_INPUT", message: "request body must be valid JSON" } },
|
|
16072
|
+
400
|
|
16073
|
+
);
|
|
16074
|
+
}
|
|
16075
|
+
if (!parsedBody || typeof parsedBody !== "object" || Array.isArray(parsedBody)) {
|
|
16076
|
+
return c.json(
|
|
16077
|
+
{
|
|
16078
|
+
ok: false,
|
|
16079
|
+
error: { code: "INVALID_INPUT", message: "request body must be a JSON object" }
|
|
16080
|
+
},
|
|
16081
|
+
400
|
|
16082
|
+
);
|
|
16083
|
+
}
|
|
16084
|
+
const body = parsedBody;
|
|
15937
16085
|
if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
|
|
15938
16086
|
return c.json(
|
|
15939
16087
|
{
|
|
@@ -15946,6 +16094,58 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
15946
16094
|
400
|
|
15947
16095
|
);
|
|
15948
16096
|
}
|
|
16097
|
+
const message = body.message;
|
|
16098
|
+
let image;
|
|
16099
|
+
if (body.image !== void 0) {
|
|
16100
|
+
const candidate = body.image;
|
|
16101
|
+
if (!candidate || typeof candidate !== "object" || !isPlaygroundImageMediaType(candidate.mediaType) || typeof candidate.data !== "string") {
|
|
16102
|
+
return c.json(
|
|
16103
|
+
{
|
|
16104
|
+
ok: false,
|
|
16105
|
+
error: { code: "INVALID_IMAGE", message: "image must be a supported base64 image" }
|
|
16106
|
+
},
|
|
16107
|
+
400
|
|
16108
|
+
);
|
|
16109
|
+
}
|
|
16110
|
+
if (candidate.data.length > PLAYGROUND_IMAGE_MAX_BASE64_CHARACTERS) {
|
|
16111
|
+
return c.json(
|
|
16112
|
+
{ ok: false, error: { code: "IMAGE_TOO_LARGE", message: "image must be at most 5 MiB" } },
|
|
16113
|
+
400
|
|
16114
|
+
);
|
|
16115
|
+
}
|
|
16116
|
+
const bytes = base64ByteLength(candidate.data);
|
|
16117
|
+
if (bytes === void 0) {
|
|
16118
|
+
return c.json(
|
|
16119
|
+
{
|
|
16120
|
+
ok: false,
|
|
16121
|
+
error: { code: "INVALID_IMAGE", message: "image data must be valid base64" }
|
|
16122
|
+
},
|
|
16123
|
+
400
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
if (bytes > PLAYGROUND_IMAGE_MAX_BYTES) {
|
|
16127
|
+
return c.json(
|
|
16128
|
+
{
|
|
16129
|
+
ok: false,
|
|
16130
|
+
error: {
|
|
16131
|
+
code: "IMAGE_TOO_LARGE",
|
|
16132
|
+
message: `image must be at most ${PLAYGROUND_IMAGE_MAX_BYTES / 1024 / 1024} MiB`
|
|
16133
|
+
}
|
|
16134
|
+
},
|
|
16135
|
+
400
|
|
16136
|
+
);
|
|
16137
|
+
}
|
|
16138
|
+
if (!hasMatchingImageSignature(candidate.mediaType, candidate.data)) {
|
|
16139
|
+
return c.json(
|
|
16140
|
+
{
|
|
16141
|
+
ok: false,
|
|
16142
|
+
error: { code: "INVALID_IMAGE", message: "image data does not match its media type" }
|
|
16143
|
+
},
|
|
16144
|
+
400
|
|
16145
|
+
);
|
|
16146
|
+
}
|
|
16147
|
+
image = { mediaType: candidate.mediaType, data: candidate.data };
|
|
16148
|
+
}
|
|
15949
16149
|
const agents = runtime.getAgents();
|
|
15950
16150
|
const agent = body.agent ? agents.find((a) => a._name === body.agent) : agents[0];
|
|
15951
16151
|
if (!agent) {
|
|
@@ -15960,14 +16160,18 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
15960
16160
|
const sessionId = body.sessionId ?? `playground-${Date.now()}`;
|
|
15961
16161
|
const store = runtime.getStateStore();
|
|
15962
16162
|
const history = await store.getSession(sessionId);
|
|
15963
|
-
history
|
|
16163
|
+
const persistedHistory = [...history, { role: "user", content: message }];
|
|
15964
16164
|
const redactOn = runtime.isRedactEnabled();
|
|
15965
16165
|
const ctx = runtime.createContext({ sessionHistory: history });
|
|
15966
16166
|
const executionId = ctx.executionId;
|
|
15967
16167
|
void ctx.events;
|
|
15968
16168
|
const traceListener = (event) => {
|
|
15969
16169
|
if (event.executionId !== executionId) return;
|
|
15970
|
-
|
|
16170
|
+
const safeEvent = image ? sanitizeRichInputFailure(event) : event;
|
|
16171
|
+
connMgr.broadcastWithWildcard(
|
|
16172
|
+
`execution:${executionId}`,
|
|
16173
|
+
redactStreamEvent(safeEvent, redactOn)
|
|
16174
|
+
);
|
|
15971
16175
|
};
|
|
15972
16176
|
runtime.on("trace", traceListener);
|
|
15973
16177
|
(async () => {
|
|
@@ -15980,10 +16184,17 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
15980
16184
|
});
|
|
15981
16185
|
try {
|
|
15982
16186
|
const schema = DEMO_SCHEMA_BY_AGENT[agent._name];
|
|
15983
|
-
const
|
|
16187
|
+
const input = image ? [
|
|
16188
|
+
{ type: "text", text: message },
|
|
16189
|
+
{
|
|
16190
|
+
type: "image",
|
|
16191
|
+
source: { type: "base64", data: image.data, mediaType: image.mediaType }
|
|
16192
|
+
}
|
|
16193
|
+
] : message;
|
|
16194
|
+
const result = await ctx.ask(agent, input, schema ? { schema } : void 0);
|
|
15984
16195
|
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
15985
|
-
|
|
15986
|
-
await store.saveSession(sessionId,
|
|
16196
|
+
persistedHistory.push({ role: "assistant", content: resultText });
|
|
16197
|
+
await store.saveSession(sessionId, persistedHistory);
|
|
15987
16198
|
const doneEvent = {
|
|
15988
16199
|
...terminalFields(),
|
|
15989
16200
|
type: "done",
|
|
@@ -15997,7 +16208,11 @@ function createPlaygroundRoutes(connMgr) {
|
|
|
15997
16208
|
const errorEvent = {
|
|
15998
16209
|
...terminalFields(),
|
|
15999
16210
|
type: "error",
|
|
16000
|
-
|
|
16211
|
+
// Media payloads can make providers echo user-controlled content in
|
|
16212
|
+
// errors. The manual terminal event is deliberately fixed and safe.
|
|
16213
|
+
data: {
|
|
16214
|
+
message: image ? "Playground media input failed" : err instanceof Error ? err.message : String(err)
|
|
16215
|
+
}
|
|
16001
16216
|
};
|
|
16002
16217
|
connMgr.broadcastWithWildcard(
|
|
16003
16218
|
`execution:${executionId}`,
|
|
@@ -16246,4 +16461,4 @@ export {
|
|
|
16246
16461
|
EvalAggregator,
|
|
16247
16462
|
createServer
|
|
16248
16463
|
};
|
|
16249
|
-
//# sourceMappingURL=chunk-
|
|
16464
|
+
//# sourceMappingURL=chunk-Z374DI7D.js.map
|