@anvia/studio 0.5.14 → 0.6.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.
Files changed (41) hide show
  1. package/README.md +62 -0
  2. package/dist/index.d.ts +78 -2
  3. package/dist/index.js +506 -9
  4. package/dist/index.js.map +1 -1
  5. package/dist/ui/assets/{ArrowClockwise.es-cF7OPZy5.js → ArrowClockwise.es-BA79KX3F.js} +2 -2
  6. package/dist/ui/assets/{ArrowClockwise.es-cF7OPZy5.js.map → ArrowClockwise.es-BA79KX3F.js.map} +1 -1
  7. package/dist/ui/assets/{Path.es-D-NB-Xon.js → Path.es-DTZ3zsLn.js} +2 -2
  8. package/dist/ui/assets/{Path.es-D-NB-Xon.js.map → Path.es-DTZ3zsLn.js.map} +1 -1
  9. package/dist/ui/assets/{Play.es-BoMnJL28.js → Play.es-CJTRfv9R.js} +2 -2
  10. package/dist/ui/assets/{Play.es-BoMnJL28.js.map → Play.es-CJTRfv9R.js.map} +1 -1
  11. package/dist/ui/assets/{evals-page-Cx4Ycr-Y.js → evals-page-rS-P7eLF.js} +2 -2
  12. package/dist/ui/assets/{evals-page-Cx4Ycr-Y.js.map → evals-page-rS-P7eLF.js.map} +1 -1
  13. package/dist/ui/assets/index-B1s3oC-V.css +1 -0
  14. package/dist/ui/assets/index-ChlKOnbD.js +54 -0
  15. package/dist/ui/assets/index-ChlKOnbD.js.map +1 -0
  16. package/dist/ui/assets/{knowledge-page-DASnGzYP.js → knowledge-page-CR0JUneq.js} +2 -2
  17. package/dist/ui/assets/{knowledge-page-DASnGzYP.js.map → knowledge-page-CR0JUneq.js.map} +1 -1
  18. package/dist/ui/assets/{mcps-page-B7VAqsoU.js → mcps-page-D3P0UAWf.js} +2 -2
  19. package/dist/ui/assets/{mcps-page-B7VAqsoU.js.map → mcps-page-D3P0UAWf.js.map} +1 -1
  20. package/dist/ui/assets/{memory-page-BCv19Tw_.js → memory-page-kVDqOAOd.js} +2 -2
  21. package/dist/ui/assets/{memory-page-BCv19Tw_.js.map → memory-page-kVDqOAOd.js.map} +1 -1
  22. package/dist/ui/assets/{pipelines-page-Byb_JYxi.js → pipelines-page-lMfsVNRy.js} +2 -2
  23. package/dist/ui/assets/{pipelines-page-Byb_JYxi.js.map → pipelines-page-lMfsVNRy.js.map} +1 -1
  24. package/dist/ui/assets/{renderers-C8RHOlPz.js → renderers-Bc89UGTN.js} +2 -2
  25. package/dist/ui/assets/{renderers-C8RHOlPz.js.map → renderers-Bc89UGTN.js.map} +1 -1
  26. package/dist/ui/assets/{sessions-page-CV8GZKjN.js → sessions-page-CVlkRPfT.js} +2 -2
  27. package/dist/ui/assets/{sessions-page-CV8GZKjN.js.map → sessions-page-CVlkRPfT.js.map} +1 -1
  28. package/dist/ui/assets/{status-page-a6SMAcLu.js → status-page-XKvTGruQ.js} +2 -2
  29. package/dist/ui/assets/{status-page-a6SMAcLu.js.map → status-page-XKvTGruQ.js.map} +1 -1
  30. package/dist/ui/assets/{tools-page-Dpj7hI8q.js → tools-page-DJt_QdDe.js} +2 -2
  31. package/dist/ui/assets/{tools-page-Dpj7hI8q.js.map → tools-page-DJt_QdDe.js.map} +1 -1
  32. package/dist/ui/assets/{trace-browser-dvGDi7ji.js → trace-browser-BBITQELO.js} +2 -2
  33. package/dist/ui/assets/{trace-browser-dvGDi7ji.js.map → trace-browser-BBITQELO.js.map} +1 -1
  34. package/dist/ui/assets/{transcript-item-GmOQdc53.js → transcript-item-BvaA1PBu.js} +3 -3
  35. package/dist/ui/assets/transcript-item-BvaA1PBu.js.map +1 -0
  36. package/dist/ui/index.html +2 -2
  37. package/package.json +1 -1
  38. package/dist/ui/assets/index-BBfu6qlV.css +0 -1
  39. package/dist/ui/assets/index-rc2RWnWD.js +0 -54
  40. package/dist/ui/assets/index-rc2RWnWD.js.map +0 -1
  41. package/dist/ui/assets/transcript-item-GmOQdc53.js.map +0 -1
package/dist/index.js CHANGED
@@ -835,16 +835,29 @@ function transcriptFromMessages(messages) {
835
835
  continue;
836
836
  }
837
837
  if (message.role === "user") {
838
+ const attachments = attachmentsFromMessage(message);
839
+ let textEntryAdded = false;
838
840
  for (const content of message.content) {
839
841
  if (content.type === "text") {
840
842
  transcript.push({
841
843
  entryId: transcript.length,
842
844
  kind: "message",
843
845
  role: "user",
844
- text: content.text
846
+ text: content.text,
847
+ ...attachments.length === 0 ? {} : { attachments }
845
848
  });
849
+ textEntryAdded = true;
846
850
  }
847
851
  }
852
+ if (!textEntryAdded && attachments.length > 0) {
853
+ transcript.push({
854
+ entryId: transcript.length,
855
+ kind: "message",
856
+ role: "user",
857
+ text: "",
858
+ attachments
859
+ });
860
+ }
848
861
  continue;
849
862
  }
850
863
  if (message.role === "tool") {
@@ -885,6 +898,32 @@ function transcriptFromMessages(messages) {
885
898
  }
886
899
  return transcript;
887
900
  }
901
+ function attachmentsFromMessage(message) {
902
+ if (message.role !== "user" && message.role !== "assistant") {
903
+ return [];
904
+ }
905
+ return message.content.flatMap((content) => {
906
+ if (content.type === "image") {
907
+ return [
908
+ {
909
+ kind: "image",
910
+ ...content.source.type === "base64" ? { data: content.source.data, mediaType: content.source.mediaType } : { url: content.source.url }
911
+ }
912
+ ];
913
+ }
914
+ if (content.type === "document") {
915
+ return [
916
+ {
917
+ kind: "document",
918
+ ...content.source.filename === void 0 ? {} : { name: content.source.filename },
919
+ ...content.source.mediaType === void 0 ? {} : { mediaType: content.source.mediaType },
920
+ ...content.source.type === "base64" ? { data: content.source.data } : content.source.type === "url" ? { url: content.source.url } : {}
921
+ }
922
+ ];
923
+ }
924
+ return [];
925
+ });
926
+ }
888
927
  function appendAssistantTranscriptText(transcript, text) {
889
928
  const last = transcript.at(-1);
890
929
  if (last?.kind === "message" && last.role === "assistant") {
@@ -940,6 +979,19 @@ var InMemoryStudioStore = class {
940
979
  const session = this.sessions.get(id);
941
980
  return session === void 0 ? void 0 : materializeSession(session);
942
981
  }
982
+ updateSessionMetadata(id, metadata) {
983
+ const session = this.sessions.get(id);
984
+ if (session === void 0) {
985
+ return void 0;
986
+ }
987
+ if (metadata === void 0) {
988
+ delete session.metadata;
989
+ } else {
990
+ session.metadata = metadata;
991
+ }
992
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
993
+ return materializeSession(session);
994
+ }
943
995
  load(context) {
944
996
  return Promise.resolve(this.sessions.get(context.sessionId)?.messages ?? []);
945
997
  }
@@ -1145,6 +1197,342 @@ function isJsonValue(value) {
1145
1197
  return isJsonObject(value) && Object.values(value).every(isJsonValue);
1146
1198
  }
1147
1199
 
1200
+ // src/runtime/models.ts
1201
+ var STUDIO_MODEL_METADATA_KEY = "studioModel";
1202
+ function createStudioModelRegistry(config) {
1203
+ if (config === void 0) {
1204
+ return void 0;
1205
+ }
1206
+ const providers = /* @__PURE__ */ new Map();
1207
+ for (const provider of config.providers) {
1208
+ const id = normalizeProviderId(provider.id);
1209
+ if (providers.has(id)) {
1210
+ throw new Error(`Duplicate Studio model provider id: ${id}`);
1211
+ }
1212
+ const staticModels = /* @__PURE__ */ new Map();
1213
+ for (const model of provider.models ?? []) {
1214
+ const modelId = normalizeModelId(model.id);
1215
+ if (staticModels.has(modelId)) {
1216
+ throw new Error(`Duplicate Studio model id for provider ${id}: ${modelId}`);
1217
+ }
1218
+ staticModels.set(modelId, { ...model, id: modelId });
1219
+ }
1220
+ providers.set(id, {
1221
+ ...provider,
1222
+ id,
1223
+ ...provider.defaultModel === void 0 ? {} : { defaultModel: normalizeModelId(provider.defaultModel) },
1224
+ staticModels
1225
+ });
1226
+ }
1227
+ return {
1228
+ ...config.default === void 0 ? {} : { defaultModel: normalizeModelRef(config.default) },
1229
+ providers,
1230
+ agentPolicies: normalizeAgentPolicies(config.agents ?? {}),
1231
+ modelCache: /* @__PURE__ */ new Map()
1232
+ };
1233
+ }
1234
+ function studioModelsConfig(registry, agents) {
1235
+ if (registry === void 0) {
1236
+ return void 0;
1237
+ }
1238
+ const providers = [...registry.providers.values()].map((provider) => {
1239
+ const models = [...provider.staticModels.values()].map(
1240
+ (model) => modelSummary(provider, model.id, model)
1241
+ );
1242
+ return {
1243
+ id: provider.id,
1244
+ ...provider.name === void 0 ? {} : { name: provider.name },
1245
+ ...provider.defaultModel === void 0 ? {} : { defaultModel: provider.defaultModel },
1246
+ models,
1247
+ ...provider.metadata === void 0 ? {} : { metadata: provider.metadata }
1248
+ };
1249
+ });
1250
+ const agentIds = new Set(agents.map((agent) => agent.id));
1251
+ const agentsConfig = Object.fromEntries(
1252
+ [...registry.agentPolicies.entries()].filter(([agentId]) => agentIds.has(agentId)).map(([agentId, policy]) => [agentId, publicPolicy(policy)])
1253
+ );
1254
+ return {
1255
+ providers,
1256
+ ...registry.defaultModel === void 0 ? {} : { default: registry.defaultModel },
1257
+ agents: agentsConfig
1258
+ };
1259
+ }
1260
+ function registerModelRoutes(app, props) {
1261
+ app.get("/models", async (c) => {
1262
+ if (props.registry === void 0) {
1263
+ return errorResponse(c, 404, "not_found", "Model registry not configured");
1264
+ }
1265
+ const providers = await Promise.all(
1266
+ [...props.registry.providers.values()].map((provider) => providerCatalog(provider))
1267
+ );
1268
+ return c.json({
1269
+ providers,
1270
+ ...props.registry.defaultModel === void 0 ? {} : { defaultModel: props.registry.defaultModel }
1271
+ });
1272
+ });
1273
+ app.get("/models/:providerId", async (c) => {
1274
+ if (props.registry === void 0) {
1275
+ return errorResponse(c, 404, "not_found", "Model registry not configured");
1276
+ }
1277
+ const provider = props.registry.providers.get(c.req.param("providerId"));
1278
+ if (provider === void 0) {
1279
+ return errorResponse(c, 404, "not_found", "Model provider not found");
1280
+ }
1281
+ return c.json(await providerCatalog(provider));
1282
+ });
1283
+ app.get("/agents/:agentId/models", async (c) => {
1284
+ const agentId = c.req.param("agentId");
1285
+ const agent = props.agentMap.get(agentId);
1286
+ if (agent === void 0) {
1287
+ return errorResponse(c, 404, "not_found", "Agent not found");
1288
+ }
1289
+ if (props.registry === void 0) {
1290
+ return c.json({
1291
+ agentId,
1292
+ models: []
1293
+ });
1294
+ }
1295
+ return c.json(await agentModelsCatalog(props.registry, agent));
1296
+ });
1297
+ }
1298
+ function resolveStudioModel(registry, input) {
1299
+ if (registry === void 0) {
1300
+ return { warnings: [] };
1301
+ }
1302
+ const selectedRef = normalizeOptionalModelRef(input.request.model) ?? sessionModelRef(input.sessionMetadata) ?? registry.agentPolicies.get(input.agent.id)?.default ?? registry.defaultModel;
1303
+ if (selectedRef === void 0) {
1304
+ return { warnings: [] };
1305
+ }
1306
+ ensureModelAllowed(registry, input.agent.id, selectedRef);
1307
+ const { providerId, modelId } = parseModelRef(selectedRef);
1308
+ const provider = registry.providers.get(providerId);
1309
+ if (provider === void 0) {
1310
+ throw new ModelSelectionError(`Unknown model provider: ${providerId}`);
1311
+ }
1312
+ let model = registry.modelCache.get(selectedRef);
1313
+ if (model === void 0) {
1314
+ model = provider.createCompletionModel(modelId);
1315
+ registry.modelCache.set(selectedRef, model);
1316
+ }
1317
+ const metadata = provider.staticModels.get(modelId);
1318
+ return {
1319
+ ref: selectedRef,
1320
+ model,
1321
+ warnings: modelWarnings(selectedRef, metadata, input.request)
1322
+ };
1323
+ }
1324
+ function sessionModelRef(metadata) {
1325
+ const value = metadata?.[STUDIO_MODEL_METADATA_KEY];
1326
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1327
+ }
1328
+ function normalizeOptionalModelRef(ref) {
1329
+ return ref === void 0 ? void 0 : normalizeModelRef(ref);
1330
+ }
1331
+ function normalizeModelRef(ref) {
1332
+ if (typeof ref === "string") {
1333
+ const trimmed = ref.trim();
1334
+ const parsed = parseModelRef(trimmed);
1335
+ return `${parsed.providerId}:${parsed.modelId}`;
1336
+ }
1337
+ return `${normalizeProviderId(ref.provider)}:${normalizeModelId(ref.model)}`;
1338
+ }
1339
+ function parseModelRef(ref) {
1340
+ const index = ref.indexOf(":");
1341
+ if (index <= 0 || index === ref.length - 1) {
1342
+ throw new ModelSelectionError(`Model ref must use provider:model format: ${ref}`);
1343
+ }
1344
+ return {
1345
+ providerId: normalizeProviderId(ref.slice(0, index)),
1346
+ modelId: normalizeModelId(ref.slice(index + 1))
1347
+ };
1348
+ }
1349
+ var ModelSelectionError = class extends Error {
1350
+ constructor(message) {
1351
+ super(message);
1352
+ this.name = "ModelSelectionError";
1353
+ }
1354
+ };
1355
+ async function agentModelsCatalog(registry, agent) {
1356
+ const policy = registry.agentPolicies.get(agent.id);
1357
+ const catalogs = await Promise.all([...registry.providers.values()].map(providerCatalog));
1358
+ const warnings = catalogs.flatMap(
1359
+ (catalog) => catalog.warning === void 0 ? [] : [{ providerId: catalog.id, warning: catalog.warning }]
1360
+ );
1361
+ const models = catalogs.flatMap((catalog) => catalog.models).filter((model) => {
1362
+ if (policy?.allowed === void 0) {
1363
+ return true;
1364
+ }
1365
+ return allowedByPolicy(policy.allowed, model.ref);
1366
+ });
1367
+ const exactPolicyModels = (policy?.allowed ?? []).filter((allowed) => !allowed.endsWith(":*")).filter((allowed) => models.every((model) => model.ref !== allowed)).flatMap((ref) => {
1368
+ try {
1369
+ const { providerId, modelId } = parseModelRef(ref);
1370
+ const provider = registry.providers.get(providerId);
1371
+ return provider === void 0 ? [] : [
1372
+ modelSummary(provider, modelId, {
1373
+ id: modelId
1374
+ })
1375
+ ];
1376
+ } catch {
1377
+ return [];
1378
+ }
1379
+ });
1380
+ const defaultModel = policy?.default ?? registry.defaultModel;
1381
+ return {
1382
+ agentId: agent.id,
1383
+ ...defaultModel === void 0 ? {} : { defaultModel },
1384
+ models: [...models, ...exactPolicyModels],
1385
+ ...warnings.length === 0 ? {} : { warnings }
1386
+ };
1387
+ }
1388
+ async function providerCatalog(provider) {
1389
+ const models = /* @__PURE__ */ new Map();
1390
+ for (const model of provider.staticModels.values()) {
1391
+ models.set(model.id, modelSummary(provider, model.id, model));
1392
+ }
1393
+ let warning;
1394
+ if (provider.listModels !== void 0) {
1395
+ try {
1396
+ const listed = await provider.listModels();
1397
+ for (const model of listed.data) {
1398
+ const staticModel = provider.staticModels.get(model.id);
1399
+ models.set(
1400
+ model.id,
1401
+ modelSummary(provider, model.id, {
1402
+ id: model.id,
1403
+ ...model.name === void 0 ? {} : { name: model.name },
1404
+ ...model.description === void 0 ? {} : { description: model.description },
1405
+ ...staticModel ?? {},
1406
+ metadata: {
1407
+ ...model.type === void 0 ? {} : { type: model.type },
1408
+ ...model.createdAt === void 0 ? {} : { createdAt: model.createdAt },
1409
+ ...model.ownedBy === void 0 ? {} : { ownedBy: model.ownedBy },
1410
+ ...model.contextLength === void 0 ? {} : { contextLength: model.contextLength },
1411
+ ...staticModel?.metadata ?? {}
1412
+ }
1413
+ })
1414
+ );
1415
+ }
1416
+ } catch (error) {
1417
+ const serialized = serializeError(error);
1418
+ warning = typeof serialized === "object" && serialized !== null && "message" in serialized ? String(serialized.message) : String(error);
1419
+ }
1420
+ }
1421
+ return {
1422
+ id: provider.id,
1423
+ ...provider.name === void 0 ? {} : { name: provider.name },
1424
+ ...provider.defaultModel === void 0 ? {} : { defaultModel: provider.defaultModel },
1425
+ models: [...models.values()].sort((left, right) => left.ref.localeCompare(right.ref)),
1426
+ ...provider.metadata === void 0 ? {} : { metadata: provider.metadata },
1427
+ ...warning === void 0 ? {} : { warning }
1428
+ };
1429
+ }
1430
+ function modelSummary(provider, modelId, model) {
1431
+ return {
1432
+ ...model,
1433
+ id: modelId,
1434
+ ref: `${provider.id}:${modelId}`,
1435
+ providerId: provider.id,
1436
+ ...provider.name === void 0 ? {} : { providerName: provider.name }
1437
+ };
1438
+ }
1439
+ function ensureModelAllowed(registry, agentId, ref) {
1440
+ const { providerId } = parseModelRef(ref);
1441
+ if (!registry.providers.has(providerId)) {
1442
+ throw new ModelSelectionError(`Unknown model provider: ${providerId}`);
1443
+ }
1444
+ const policy = registry.agentPolicies.get(agentId);
1445
+ if (policy?.allowed !== void 0 && !allowedByPolicy(policy.allowed, ref)) {
1446
+ throw new ModelSelectionError(`Model ${ref} is not allowed for agent ${agentId}`);
1447
+ }
1448
+ }
1449
+ function allowedByPolicy(allowed, ref) {
1450
+ return allowed.some(
1451
+ (entry) => entry.endsWith(":*") ? ref.startsWith(entry.slice(0, -1)) : entry === ref
1452
+ );
1453
+ }
1454
+ function normalizeAgentPolicies(policies) {
1455
+ return new Map(
1456
+ Object.entries(policies).map(([agentId, policy]) => [
1457
+ agentId.trim(),
1458
+ {
1459
+ ...policy.default === void 0 ? {} : { default: normalizeModelRef(policy.default) },
1460
+ ...policy.allowed === void 0 ? {} : {
1461
+ allowed: policy.allowed.map(
1462
+ (entry) => typeof entry === "string" && entry.trim().endsWith(":*") ? `${normalizeProviderId(entry.trim().slice(0, -2))}:*` : normalizeModelRef(entry)
1463
+ )
1464
+ }
1465
+ }
1466
+ ])
1467
+ );
1468
+ }
1469
+ function publicPolicy(policy) {
1470
+ return {
1471
+ ...policy.default === void 0 ? {} : { default: policy.default },
1472
+ ...policy.allowed === void 0 ? {} : { allowed: policy.allowed }
1473
+ };
1474
+ }
1475
+ function modelWarnings(ref, model, request) {
1476
+ const warnings = [];
1477
+ const modalities = requestModalities(request);
1478
+ const missingModalities = [...modalities].filter(
1479
+ (modality) => !model?.modalities?.input.includes(modality)
1480
+ );
1481
+ if (model?.modalities !== void 0 && missingModalities.length > 0) {
1482
+ warnings.push({
1483
+ model: ref,
1484
+ kind: "modality",
1485
+ message: `Model ${ref} does not declare input support for ${missingModalities.join(", ")}`,
1486
+ missing: missingModalities
1487
+ });
1488
+ }
1489
+ if (request.stream === true && model?.capabilities?.streaming === false) {
1490
+ warnings.push({
1491
+ model: ref,
1492
+ kind: "capability",
1493
+ message: `Model ${ref} is configured as non-streaming but the run requested streaming`,
1494
+ capability: "streaming"
1495
+ });
1496
+ }
1497
+ return warnings;
1498
+ }
1499
+ function requestModalities(request) {
1500
+ const modalities = /* @__PURE__ */ new Set(["text"]);
1501
+ for (const message of requestMessages(request)) {
1502
+ if (typeof message === "string") {
1503
+ continue;
1504
+ }
1505
+ if (message.role === "user" || message.role === "assistant") {
1506
+ for (const content of message.content) {
1507
+ if (content.type === "image") {
1508
+ modalities.add("image");
1509
+ }
1510
+ if (content.type === "document") {
1511
+ modalities.add("document");
1512
+ }
1513
+ }
1514
+ }
1515
+ }
1516
+ return modalities;
1517
+ }
1518
+ function requestMessages(request) {
1519
+ return [...request.history ?? [], request.message];
1520
+ }
1521
+ function normalizeProviderId(id) {
1522
+ const trimmed = id.trim();
1523
+ if (trimmed.length === 0 || trimmed.includes(":")) {
1524
+ throw new ModelSelectionError("Model provider id must be non-empty and cannot contain ':'");
1525
+ }
1526
+ return trimmed;
1527
+ }
1528
+ function normalizeModelId(id) {
1529
+ const trimmed = id.trim();
1530
+ if (trimmed.length === 0) {
1531
+ throw new ModelSelectionError("Model id cannot be empty");
1532
+ }
1533
+ return trimmed;
1534
+ }
1535
+
1148
1536
  // src/runtime/tool-metadata.ts
1149
1537
  import { ToolSet } from "@anvia/core/tool";
1150
1538
  var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
@@ -1296,12 +1684,14 @@ function normalizePipelines(pipelines) {
1296
1684
  });
1297
1685
  }
1298
1686
  function buildConfig(options, agents, pipelines, stores) {
1687
+ const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
1299
1688
  return {
1300
1689
  id: runnerId(options),
1301
1690
  ...options.name === void 0 ? {} : { name: options.name },
1302
1691
  ...options.description === void 0 ? {} : { description: options.description },
1303
1692
  ...options.version === void 0 ? {} : { version: options.version },
1304
1693
  agents: agents.map(agentConfig),
1694
+ ...models === void 0 ? {} : { models },
1305
1695
  pipelines: pipelines.map(pipelineConfig),
1306
1696
  evals: options.evals.map(evalConfig),
1307
1697
  chat: {
@@ -3097,7 +3487,8 @@ function messageToTranscriptEntry(message, entryId) {
3097
3487
  entryId,
3098
3488
  kind: "message",
3099
3489
  role,
3100
- text: extractMessageText(message)
3490
+ text: extractMessageText(message),
3491
+ ...role === "user" ? optionalTranscriptAttachments(message) : {}
3101
3492
  };
3102
3493
  }
3103
3494
  function appendTranscriptAssistantText(transcript, delta) {
@@ -3193,6 +3584,33 @@ function extractMessageText(message) {
3193
3584
  return [];
3194
3585
  }).join("\n");
3195
3586
  }
3587
+ function optionalTranscriptAttachments(message) {
3588
+ if (typeof message === "string" || message.role !== "user") {
3589
+ return {};
3590
+ }
3591
+ const attachments = message.content.flatMap((content) => {
3592
+ if (content.type === "image") {
3593
+ return [
3594
+ {
3595
+ kind: "image",
3596
+ ...content.source.type === "base64" ? { data: content.source.data, mediaType: content.source.mediaType } : { url: content.source.url }
3597
+ }
3598
+ ];
3599
+ }
3600
+ if (content.type === "document") {
3601
+ return [
3602
+ {
3603
+ kind: "document",
3604
+ ...content.source.filename === void 0 ? {} : { name: content.source.filename },
3605
+ ...content.source.mediaType === void 0 ? {} : { mediaType: content.source.mediaType },
3606
+ ...content.source.type === "base64" ? { data: content.source.data } : content.source.type === "url" ? { url: content.source.url } : {}
3607
+ }
3608
+ ];
3609
+ }
3610
+ return [];
3611
+ });
3612
+ return attachments.length === 0 ? {} : { attachments };
3613
+ }
3196
3614
  function formatJson2(value) {
3197
3615
  try {
3198
3616
  return JSON.stringify(value, null, 2);
@@ -3257,6 +3675,25 @@ async function parseRunRequest(c) {
3257
3675
  }
3258
3676
  request.toolConcurrency = body.toolConcurrency;
3259
3677
  }
3678
+ if ("model" in body) {
3679
+ if (typeof body.model === "string") {
3680
+ request.model = body.model;
3681
+ } else if (isObject(body.model) && typeof body.model.provider === "string" && typeof body.model.model === "string") {
3682
+ request.model = {
3683
+ provider: body.model.provider,
3684
+ model: body.model.model
3685
+ };
3686
+ } else {
3687
+ return {
3688
+ error: errorResponse(
3689
+ c,
3690
+ 400,
3691
+ "bad_request",
3692
+ "model must be a provider:model string or { provider, model } object"
3693
+ )
3694
+ };
3695
+ }
3696
+ }
3260
3697
  if ("metadata" in body) {
3261
3698
  if (!isJsonObject2(body.metadata)) {
3262
3699
  return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
@@ -4825,6 +5262,7 @@ function studioOptionsFromTargets(targets, options) {
4825
5262
  agents: inferStudioAgents(agents, options.quickPrompts ?? {}),
4826
5263
  pipelines: inferStudioPipelines(pipelines),
4827
5264
  evals: options.evals ?? [],
5265
+ ...options.models === void 0 ? {} : { models: options.models },
4828
5266
  ...options.stores === void 0 ? {} : { stores: options.stores },
4829
5267
  ...options.ui === void 0 ? {} : { ui: options.ui }
4830
5268
  };
@@ -4882,6 +5320,7 @@ function createStudioApp(options) {
4882
5320
  const agents = normalizeAgents(options.agents).map(
4883
5321
  (agent) => withStudioTraceObserver(agent, stores.traces)
4884
5322
  );
5323
+ const modelRegistry = createStudioModelRegistry(options.models);
4885
5324
  const pipelines = normalizePipelines(options.pipelines);
4886
5325
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
4887
5326
  const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
@@ -4924,6 +5363,7 @@ function createStudioApp(options) {
4924
5363
  }
4925
5364
  return c.json(agentRuntimeSummary(agent));
4926
5365
  });
5366
+ registerModelRoutes(app, { registry: modelRegistry, agentMap });
4927
5367
  registerMcpRoutes(app, { agentMap });
4928
5368
  registerToolRoutes(app, { agentMap });
4929
5369
  registerApprovalRoutes(app, approvalRuntime);
@@ -4963,6 +5403,20 @@ function createStudioApp(options) {
4963
5403
  if (session !== void 0 && session.agentId !== agentId) {
4964
5404
  return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
4965
5405
  }
5406
+ let selectedModel;
5407
+ try {
5408
+ selectedModel = resolveStudioModel(modelRegistry, {
5409
+ agent,
5410
+ request: body,
5411
+ sessionMetadata: session?.metadata
5412
+ });
5413
+ } catch (error) {
5414
+ if (error instanceof ModelSelectionError) {
5415
+ return errorResponse(c, 400, "bad_request", error.message);
5416
+ }
5417
+ throw error;
5418
+ }
5419
+ const runAgent = selectedModel.model === void 0 ? agent.agent : cloneAgent(agent.agent, { model: selectedModel.model });
4966
5420
  const runId = globalThis.crypto.randomUUID();
4967
5421
  const runStartedAt = Date.now();
4968
5422
  if (session !== void 0) {
@@ -4977,18 +5431,43 @@ function createStudioApp(options) {
4977
5431
  ...body.maxTurns === void 0 ? {} : { maxTurns: body.maxTurns },
4978
5432
  ...body.toolConcurrency === void 0 ? {} : { toolConcurrency: body.toolConcurrency },
4979
5433
  hasTrace: body.trace !== void 0,
4980
- ...body.metadata === void 0 ? {} : { metadata: body.metadata }
5434
+ ...body.metadata === void 0 && selectedModel.ref === void 0 ? {} : {
5435
+ metadata: {
5436
+ ...body.metadata ?? {},
5437
+ ...selectedModel.ref === void 0 ? {} : { [STUDIO_MODEL_METADATA_KEY]: selectedModel.ref }
5438
+ }
5439
+ }
4981
5440
  })
4982
5441
  );
4983
5442
  }
5443
+ if (session !== void 0 && selectedModel.ref !== void 0) {
5444
+ for (const warning of selectedModel.warnings) {
5445
+ await appendSessionLog(stores.sessions, {
5446
+ sessionId: session.id,
5447
+ runId,
5448
+ level: "warn",
5449
+ category: "model",
5450
+ event: "model.warning",
5451
+ message: typeof warning.message === "string" ? warning.message : "Model warning",
5452
+ metadata: warning
5453
+ });
5454
+ }
5455
+ if (sessionModelRef(session.metadata) !== selectedModel.ref) {
5456
+ await stores.sessions?.updateSessionMetadata?.(session.id, {
5457
+ ...session.metadata ?? {},
5458
+ [STUDIO_MODEL_METADATA_KEY]: selectedModel.ref
5459
+ });
5460
+ }
5461
+ }
4984
5462
  const memoryMetadata = {
4985
5463
  agentId,
4986
5464
  ...body.metadata ?? {},
5465
+ ...selectedModel.ref === void 0 ? {} : { [STUDIO_MODEL_METADATA_KEY]: selectedModel.ref },
4987
5466
  studioRunId: runId
4988
5467
  };
4989
5468
  const promptMessage = normalizePromptMessage(body.message);
4990
5469
  const sessionStore = stores.sessions;
4991
- const shouldPersistSessionMessages = session !== void 0 && sessionStore !== void 0 && !usesStoreAsAgentMemory(agent.agent, sessionStore);
5470
+ const shouldPersistSessionMessages = session !== void 0 && sessionStore !== void 0 && !usesStoreAsAgentMemory(runAgent, sessionStore);
4992
5471
  if (shouldPersistSessionMessages) {
4993
5472
  await sessionStore.append({
4994
5473
  context: { sessionId: session.id, metadata: memoryMetadata },
@@ -4997,7 +5476,7 @@ function createStudioApp(options) {
4997
5476
  messages: [promptMessage]
4998
5477
  });
4999
5478
  }
5000
- const request = session !== void 0 ? agent.agent.memory === void 0 ? agent.agent.prompt([...session.messages, promptMessage]) : agent.agent.session(session.id, { metadata: memoryMetadata }).prompt(body.message) : agent.agent.prompt(
5479
+ const request = session !== void 0 ? runAgent.memory === void 0 ? runAgent.prompt([...session.messages, promptMessage]) : runAgent.session(session.id, { metadata: memoryMetadata }).prompt(body.message) : runAgent.prompt(
5001
5480
  body.history !== void 0 ? [...body.history, promptMessage] : body.message
5002
5481
  );
5003
5482
  if (body.maxTurns !== void 0) {
@@ -5015,13 +5494,13 @@ function createStudioApp(options) {
5015
5494
  const runtimeEvents = new AsyncEventQueue();
5016
5495
  const effectiveHook = composeHooks(
5017
5496
  composeHooks(
5018
- agent.agent.hook,
5497
+ runAgent.hook,
5019
5498
  approvalRuntime.createHook({
5020
5499
  runId,
5021
5500
  agentId,
5022
5501
  ...session?.id === void 0 ? {} : { sessionId: session.id },
5023
5502
  ...body.metadata === void 0 ? {} : { metadata: body.metadata },
5024
- getTool: (toolName) => agent.agent.getTool(toolName),
5503
+ getTool: (toolName) => runAgent.getTool(toolName),
5025
5504
  emit: (event) => runtimeEvents.push(event)
5026
5505
  })
5027
5506
  ),
@@ -5060,13 +5539,13 @@ function createStudioApp(options) {
5060
5539
  }
5061
5540
  const effectiveHook = composeHooks(
5062
5541
  composeHooks(
5063
- agent.agent.hook,
5542
+ runAgent.hook,
5064
5543
  approvalRuntime.createHook({
5065
5544
  runId,
5066
5545
  agentId,
5067
5546
  ...session?.id === void 0 ? {} : { sessionId: session.id },
5068
5547
  ...body.metadata === void 0 ? {} : { metadata: body.metadata },
5069
- getTool: (toolName) => agent.agent.getTool(toolName)
5548
+ getTool: (toolName) => runAgent.getTool(toolName)
5070
5549
  })
5071
5550
  ),
5072
5551
  questionRuntime.createHook({
@@ -5322,6 +5801,24 @@ var SqliteSessionStore = class {
5322
5801
  const row = this.getSessionRow(id);
5323
5802
  return row === void 0 ? void 0 : toSession(row, this.listSessionMessages(id), this.listSessionRunRows(id));
5324
5803
  }
5804
+ updateSessionMetadata(id, metadata) {
5805
+ const db = this.database();
5806
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5807
+ const result = db.prepare(
5808
+ `UPDATE anvia_studio_sessions
5809
+ SET metadata_json = $metadata,
5810
+ updated_at = $now
5811
+ WHERE id = $id`
5812
+ ).run({
5813
+ $id: id,
5814
+ $metadata: metadata === void 0 ? null : JSON.stringify(metadata),
5815
+ $now: now
5816
+ });
5817
+ if (result.changes === 0) {
5818
+ return void 0;
5819
+ }
5820
+ return this.getSession(id);
5821
+ }
5325
5822
  load(context) {
5326
5823
  const session = this.getSession(context.sessionId);
5327
5824
  return Promise.resolve(session?.messages ?? []);