@percepta/kaizen 0.9.0 → 0.10.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 (81) hide show
  1. package/dist/dashboard/pages/api/langfuse-action.js +9 -2
  2. package/dist/dashboard/pages/api/langfuse-action.js.map +1 -1
  3. package/dist/dashboard/pages/api/langfuse-dataset-item.js +5 -2
  4. package/dist/dashboard/pages/api/langfuse-dataset-item.js.map +1 -1
  5. package/dist/dashboard/pages/api/langfuse-dataset-mutation.js +1 -1
  6. package/dist/dashboard/pages/api/langfuse-dataset-mutation.js.map +1 -1
  7. package/dist/dashboard/pages/api/langfuse-dataset.js.map +1 -1
  8. package/dist/dashboard/pages/api/langfuse-datasets.js.map +1 -1
  9. package/dist/dashboard/pages/api/langfuse-trace-memberships.js +1 -1
  10. package/dist/dashboard/pages/api/langfuse-trace-memberships.js.map +1 -1
  11. package/dist/dashboard/pages/api/langfuse-trace.js.map +1 -1
  12. package/dist/dashboard/pages/api/langfuse-traces.js.map +1 -1
  13. package/dist/dashboard/pages/api/linear-ideas.js.map +1 -1
  14. package/dist/dashboard/pages/api/run-events.js.map +1 -1
  15. package/dist/dashboard/pages/api/run-failures.js.map +1 -1
  16. package/dist/dashboard/pages/api/run-traces.js.map +1 -1
  17. package/dist/dashboard/pages/api/runs.js.map +1 -1
  18. package/dist/dashboard/pages/api/systems.js +1 -1
  19. package/dist/dashboard/pages/api/systems.js.map +1 -1
  20. package/dist/dashboard/pages/api/trace-renderer-version.js +10 -3
  21. package/dist/dashboard/pages/api/trace-renderer-version.js.map +1 -1
  22. package/dist/dashboard/pages/api/trace-renderer.js +12 -26
  23. package/dist/dashboard/pages/api/trace-renderer.js.map +1 -1
  24. package/dist/dashboard/src/lib/bundle-custom-renderer.js +167 -0
  25. package/dist/dashboard/src/lib/bundle-custom-renderer.js.map +1 -0
  26. package/dist/dashboard/src/lib/custom-renderer-files.js.map +1 -1
  27. package/dist/dashboard/src/lib/custom-renderer-metadata.js.map +1 -1
  28. package/dist/dashboard/src/lib/custom-view-paths.js.map +1 -1
  29. package/dist/dashboard/src/lib/dataset-item-labeling.js +20 -0
  30. package/dist/dashboard/src/lib/dataset-item-labeling.js.map +1 -0
  31. package/dist/dashboard/src/lib/env.js.map +1 -1
  32. package/dist/dashboard/src/lib/langfuse-cache.js.map +1 -1
  33. package/dist/dashboard/src/lib/langfuse-creds.js.map +1 -1
  34. package/dist/dashboard/src/lib/langfuse-demo.js.map +1 -1
  35. package/dist/dashboard/src/lib/langfuse-errors.js.map +1 -1
  36. package/dist/dashboard/src/lib/langfuse-helpers.js.map +1 -1
  37. package/dist/dashboard/src/lib/run-api.js.map +1 -1
  38. package/dist/dashboard/src/lib/run-store.js.map +1 -1
  39. package/dist/dashboard/src/lib/types.js.map +1 -1
  40. package/dist/dashboard/src/lib/workspace-env.js.map +1 -1
  41. package/dist/dashboard/src/lib/workspace.js.map +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/langfuse.d.ts.map +1 -1
  44. package/dist/langfuse.js.map +1 -1
  45. package/dist/package.js +2 -2
  46. package/dist/shared/env-file.js.map +1 -1
  47. package/dist/shared/linear-ideas.js.map +1 -1
  48. package/dist/shared/linear-issue.js.map +1 -1
  49. package/dist/shared/view-types.d.ts.map +1 -1
  50. package/dist/shared/workspace-paths.js.map +1 -1
  51. package/dist/src/commands/create-view.js.map +1 -1
  52. package/dist/src/commands/guide.js.map +1 -1
  53. package/dist/src/commands/ideas.js.map +1 -1
  54. package/dist/src/commands/init-system.js.map +1 -1
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/log.js.map +1 -1
  57. package/dist/src/commands/rebuild.js.map +1 -1
  58. package/dist/src/commands/run.js.map +1 -1
  59. package/dist/src/commands/studio.js.map +1 -1
  60. package/dist/src/lib/bootstrap.js.map +1 -1
  61. package/dist/src/lib/cli.js.map +1 -1
  62. package/dist/src/lib/events.js +2 -0
  63. package/dist/src/lib/events.js.map +1 -1
  64. package/dist/src/lib/fs-utils.js.map +1 -1
  65. package/dist/src/lib/leaderboard.js.map +1 -1
  66. package/dist/src/lib/parse-args.js.map +1 -1
  67. package/dist/src/lib/paths.js.map +1 -1
  68. package/dist/src/lib/promotion.js.map +1 -1
  69. package/dist/src/lib/prompt.js.map +1 -1
  70. package/dist/src/lib/run-dir.js.map +1 -1
  71. package/dist/src/lib/runner.js.map +1 -1
  72. package/dist/src/lib/system.js.map +1 -1
  73. package/dist/studio/client/assets/index-Dc4zGLjQ.css +1 -0
  74. package/dist/studio/client/assets/index-ElL5OoiH.js +9 -0
  75. package/dist/studio/client/index.html +2 -2
  76. package/dist/studio/server.d.ts.map +1 -1
  77. package/dist/studio/server.js.map +1 -1
  78. package/dist/types.d.ts.map +1 -1
  79. package/package.json +3 -3
  80. package/dist/studio/client/assets/index-Bwj0gucs.css +0 -1
  81. package/dist/studio/client/assets/index-DKAiSaYs.js +0 -9
@@ -1,7 +1,8 @@
1
+ import { isRecord } from "../../src/lib/langfuse-helpers.js";
2
+ import { markDatasetItemLabeled } from "../../src/lib/dataset-item-labeling.js";
1
3
  import { resolveLangfuseCreds } from "../../src/lib/langfuse-creds.js";
2
4
  import { DEMO_CONNECTION, isDemoMode } from "../../src/lib/langfuse-demo.js";
3
5
  import { formatLangfuseHttpError, formatLangfuseRequestError } from "../../src/lib/langfuse-errors.js";
4
- import { isRecord } from "../../src/lib/langfuse-helpers.js";
5
6
  import { datasetItemsCache } from "./langfuse-dataset.js";
6
7
  import { membershipsCache } from "./langfuse-trace-memberships.js";
7
8
  //#region dashboard/pages/api/langfuse-action.ts
@@ -77,12 +78,18 @@ async function dispatch(creds, request) {
77
78
  id: request.itemId,
78
79
  input: request.input === void 0 ? existing?.input : request.input,
79
80
  expectedOutput: request.expectedOutput === void 0 ? existing?.expectedOutput : request.expectedOutput,
80
- metadata: request.metadata === void 0 ? existing?.metadata : request.metadata,
81
+ metadata: getUpdatedDatasetItemMetadata(existing, request),
81
82
  sourceTraceId: request.sourceTraceId === void 0 ? existing?.sourceTraceId : request.sourceTraceId,
82
83
  status: request.status === void 0 ? existing?.status : request.status
83
84
  }
84
85
  });
85
86
  }
87
+ function getUpdatedDatasetItemMetadata(existing, request) {
88
+ const existingMetadata = existing?.metadata;
89
+ const metadata = request.metadata === void 0 ? isRecord(existingMetadata) || existingMetadata === null ? existingMetadata : void 0 : request.metadata;
90
+ if (request.expectedOutput === void 0) return metadata;
91
+ return markDatasetItemLabeled(metadata);
92
+ }
86
93
  async function langfuseGet(creds, resource, path) {
87
94
  const res = await fetch(`${creds.host.replace(/\/$/, "")}${path}`, {
88
95
  method: "GET",
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-action.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-action.ts"],"sourcesContent":["import { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport { DEMO_CONNECTION, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ntype MutationRequest =\n | {\n action: \"create-score\";\n traceId: string;\n name: string;\n value: number | string | boolean;\n comment?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n action: \"update-dataset-item\";\n datasetName: string;\n itemId: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n sourceTraceId?: string | null;\n status?: string | null;\n }\n | {\n action: \"create-dataset-run-item\";\n datasetItemId: string;\n traceId: string;\n runName: string;\n runDescription?: string;\n metadata?: Record<string, unknown>;\n };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const parsed = parseMutationRequest(req.body);\n if (!parsed.ok) {\n res.status(400).json({ error: parsed.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n res.status(200).json({\n data: null,\n success: true,\n connection: DEMO_CONNECTION,\n });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const lfCreds: LangfuseCreds = { host: creds.host, auth: creds.auth };\n\n try {\n const data = await dispatch(lfCreds, parsed.value);\n if (\n parsed.value.action === \"update-dataset-item\" ||\n parsed.value.action === \"create-dataset-run-item\"\n ) {\n datasetItemsCache.clear();\n membershipsCache.clear();\n }\n res.status(200).json({ data });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function dispatch(\n creds: LangfuseCreds,\n request: MutationRequest,\n): Promise<unknown> {\n if (request.action === \"create-score\") {\n return langfuseJson(creds, \"the score\", {\n path: \"/api/public/scores\",\n body: {\n traceId: request.traceId,\n name: request.name,\n value: request.value,\n comment: request.comment,\n metadata: request.metadata,\n },\n });\n }\n\n if (request.action === \"create-dataset-run-item\") {\n return langfuseJson(creds, \"the dataset run item\", {\n path: \"/api/public/dataset-run-items\",\n body: {\n datasetItemId: request.datasetItemId,\n traceId: request.traceId,\n runName: request.runName,\n runDescription: request.runDescription,\n metadata: request.metadata,\n },\n });\n }\n\n // Skip the upstream GET when the client supplies all fields.\n const hasAllFields =\n request.input !== undefined &&\n request.expectedOutput !== undefined &&\n request.metadata !== undefined &&\n request.sourceTraceId !== undefined &&\n request.status !== undefined;\n\n const existing = hasAllFields\n ? null\n : await langfuseGet<Record<string, unknown>>(\n creds,\n \"the dataset item\",\n `/api/public/dataset-items/${encodeURIComponent(request.itemId)}`,\n );\n return langfuseJson(creds, \"the dataset item\", {\n path: \"/api/public/dataset-items\",\n body: {\n datasetName: request.datasetName,\n id: request.itemId,\n input: request.input === undefined ? existing?.input : request.input,\n expectedOutput:\n request.expectedOutput === undefined\n ? existing?.expectedOutput\n : request.expectedOutput,\n metadata:\n request.metadata === undefined ? existing?.metadata : request.metadata,\n sourceTraceId:\n request.sourceTraceId === undefined\n ? existing?.sourceTraceId\n : request.sourceTraceId,\n status: request.status === undefined ? existing?.status : request.status,\n },\n });\n}\n\nasync function langfuseGet<T>(\n creds: LangfuseCreds,\n resource: string,\n path: string,\n): Promise<T> {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${path}`, {\n method: \"GET\",\n headers: { Authorization: `Basic ${creds.auth}` },\n });\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, resource),\n );\n }\n return res.json() as Promise<T>;\n}\n\nasync function langfuseJson(\n creds: LangfuseCreds,\n resource: string,\n request: { path: string; body: unknown },\n): Promise<unknown> {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${request.path}`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request.body),\n });\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, resource),\n );\n }\n return res.json();\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n\n if (action === \"create-score\") {\n const traceId = stringField(value, \"traceId\");\n const name = stringField(value, \"name\");\n if (!traceId) return { ok: false, error: \"traceId is required.\" };\n if (!name) return { ok: false, error: \"name is required.\" };\n if (!isScoreValue(value.value)) {\n return {\n ok: false,\n error: \"value must be a number, string, or boolean.\",\n };\n }\n return {\n ok: true,\n value: {\n action,\n traceId,\n name,\n value: value.value,\n comment: stringField(value, \"comment\") ?? undefined,\n metadata: recordField(value, \"metadata\") ?? undefined,\n },\n };\n }\n\n if (action === \"update-dataset-item\") {\n const datasetName = stringField(value, \"datasetName\");\n const itemId = stringField(value, \"itemId\");\n if (!datasetName) return { ok: false, error: \"datasetName is required.\" };\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata: recordOrNullField(value, \"metadata\"),\n sourceTraceId: stringOrNullField(value, \"sourceTraceId\"),\n status: stringOrNullField(value, \"status\"),\n },\n };\n }\n\n if (action === \"create-dataset-run-item\") {\n const datasetItemId = stringField(value, \"datasetItemId\");\n const traceId = stringField(value, \"traceId\");\n const runName = stringField(value, \"runName\");\n if (!datasetItemId)\n return { ok: false, error: \"datasetItemId is required.\" };\n if (!traceId) return { ok: false, error: \"traceId is required.\" };\n if (!runName) return { ok: false, error: \"runName is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetItemId,\n traceId,\n runName,\n runDescription: stringField(value, \"runDescription\") ?? undefined,\n metadata: recordField(value, \"metadata\") ?? undefined,\n },\n };\n }\n\n return {\n ok: false,\n error:\n \"action must be create-score, update-dataset-item, or create-dataset-run-item.\",\n };\n}\n\nfunction isScoreValue(value: unknown): value is number | string | boolean {\n return (\n (typeof value === \"number\" && Number.isFinite(value)) ||\n typeof value === \"string\" ||\n typeof value === \"boolean\"\n );\n}\n\nfunction recordField(\n value: Record<string, unknown>,\n key: string,\n): Record<string, unknown> | null {\n const field = value[key];\n return isRecord(field) ? field : null;\n}\n\nfunction recordOrNullField(\n value: Record<string, unknown>,\n key: string,\n): Record<string, unknown> | null | undefined {\n const field = value[key];\n if (field === null) return null;\n return isRecord(field) ? field : undefined;\n}\n\nfunction stringOrNullField(\n value: Record<string, unknown>,\n key: string,\n): string | null | undefined {\n const field = value[key];\n if (field === null) return null;\n return typeof field === \"string\" ? field : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-action.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-action.ts"],"sourcesContent":["import { markDatasetItemLabeled } from \"../../src/lib/dataset-item-labeling\";\nimport { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport { DEMO_CONNECTION, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ntype MutationRequest =\n | {\n action: \"create-score\";\n traceId: string;\n name: string;\n value: number | string | boolean;\n comment?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n action: \"update-dataset-item\";\n datasetName: string;\n itemId: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n sourceTraceId?: string | null;\n status?: string | null;\n }\n | {\n action: \"create-dataset-run-item\";\n datasetItemId: string;\n traceId: string;\n runName: string;\n runDescription?: string;\n metadata?: Record<string, unknown>;\n };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const parsed = parseMutationRequest(req.body);\n if (!parsed.ok) {\n res.status(400).json({ error: parsed.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n res.status(200).json({\n data: null,\n success: true,\n connection: DEMO_CONNECTION,\n });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const lfCreds: LangfuseCreds = { host: creds.host, auth: creds.auth };\n\n try {\n const data = await dispatch(lfCreds, parsed.value);\n if (\n parsed.value.action === \"update-dataset-item\" ||\n parsed.value.action === \"create-dataset-run-item\"\n ) {\n datasetItemsCache.clear();\n membershipsCache.clear();\n }\n res.status(200).json({ data });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function dispatch(\n creds: LangfuseCreds,\n request: MutationRequest,\n): Promise<unknown> {\n if (request.action === \"create-score\") {\n return langfuseJson(creds, \"the score\", {\n path: \"/api/public/scores\",\n body: {\n traceId: request.traceId,\n name: request.name,\n value: request.value,\n comment: request.comment,\n metadata: request.metadata,\n },\n });\n }\n\n if (request.action === \"create-dataset-run-item\") {\n return langfuseJson(creds, \"the dataset run item\", {\n path: \"/api/public/dataset-run-items\",\n body: {\n datasetItemId: request.datasetItemId,\n traceId: request.traceId,\n runName: request.runName,\n runDescription: request.runDescription,\n metadata: request.metadata,\n },\n });\n }\n\n // Skip the upstream GET when the client supplies all fields.\n const hasAllFields =\n request.input !== undefined &&\n request.expectedOutput !== undefined &&\n request.metadata !== undefined &&\n request.sourceTraceId !== undefined &&\n request.status !== undefined;\n\n const existing = hasAllFields\n ? null\n : await langfuseGet<Record<string, unknown>>(\n creds,\n \"the dataset item\",\n `/api/public/dataset-items/${encodeURIComponent(request.itemId)}`,\n );\n return langfuseJson(creds, \"the dataset item\", {\n path: \"/api/public/dataset-items\",\n body: {\n datasetName: request.datasetName,\n id: request.itemId,\n input: request.input === undefined ? existing?.input : request.input,\n expectedOutput:\n request.expectedOutput === undefined\n ? existing?.expectedOutput\n : request.expectedOutput,\n metadata: getUpdatedDatasetItemMetadata(existing, request),\n sourceTraceId:\n request.sourceTraceId === undefined\n ? existing?.sourceTraceId\n : request.sourceTraceId,\n status: request.status === undefined ? existing?.status : request.status,\n },\n });\n}\n\nexport function getUpdatedDatasetItemMetadata(\n existing: Record<string, unknown> | null,\n request: Extract<MutationRequest, { action: \"update-dataset-item\" }>,\n): Record<string, unknown> | null | undefined {\n const existingMetadata = existing?.metadata;\n const metadata =\n request.metadata === undefined\n ? isRecord(existingMetadata) || existingMetadata === null\n ? existingMetadata\n : undefined\n : request.metadata;\n if (request.expectedOutput === undefined) return metadata;\n return markDatasetItemLabeled(metadata);\n}\n\nasync function langfuseGet<T>(\n creds: LangfuseCreds,\n resource: string,\n path: string,\n): Promise<T> {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${path}`, {\n method: \"GET\",\n headers: { Authorization: `Basic ${creds.auth}` },\n });\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, resource),\n );\n }\n return res.json() as Promise<T>;\n}\n\nasync function langfuseJson(\n creds: LangfuseCreds,\n resource: string,\n request: { path: string; body: unknown },\n): Promise<unknown> {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${request.path}`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request.body),\n });\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, resource),\n );\n }\n return res.json();\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n\n if (action === \"create-score\") {\n const traceId = stringField(value, \"traceId\");\n const name = stringField(value, \"name\");\n if (!traceId) return { ok: false, error: \"traceId is required.\" };\n if (!name) return { ok: false, error: \"name is required.\" };\n if (!isScoreValue(value.value)) {\n return {\n ok: false,\n error: \"value must be a number, string, or boolean.\",\n };\n }\n return {\n ok: true,\n value: {\n action,\n traceId,\n name,\n value: value.value,\n comment: stringField(value, \"comment\") ?? undefined,\n metadata: recordField(value, \"metadata\") ?? undefined,\n },\n };\n }\n\n if (action === \"update-dataset-item\") {\n const datasetName = stringField(value, \"datasetName\");\n const itemId = stringField(value, \"itemId\");\n if (!datasetName) return { ok: false, error: \"datasetName is required.\" };\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata: recordOrNullField(value, \"metadata\"),\n sourceTraceId: stringOrNullField(value, \"sourceTraceId\"),\n status: stringOrNullField(value, \"status\"),\n },\n };\n }\n\n if (action === \"create-dataset-run-item\") {\n const datasetItemId = stringField(value, \"datasetItemId\");\n const traceId = stringField(value, \"traceId\");\n const runName = stringField(value, \"runName\");\n if (!datasetItemId)\n return { ok: false, error: \"datasetItemId is required.\" };\n if (!traceId) return { ok: false, error: \"traceId is required.\" };\n if (!runName) return { ok: false, error: \"runName is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetItemId,\n traceId,\n runName,\n runDescription: stringField(value, \"runDescription\") ?? undefined,\n metadata: recordField(value, \"metadata\") ?? undefined,\n },\n };\n }\n\n return {\n ok: false,\n error:\n \"action must be create-score, update-dataset-item, or create-dataset-run-item.\",\n };\n}\n\nfunction isScoreValue(value: unknown): value is number | string | boolean {\n return (\n (typeof value === \"number\" && Number.isFinite(value)) ||\n typeof value === \"string\" ||\n typeof value === \"boolean\"\n );\n}\n\nfunction recordField(\n value: Record<string, unknown>,\n key: string,\n): Record<string, unknown> | null {\n const field = value[key];\n return isRecord(field) ? field : null;\n}\n\nfunction recordOrNullField(\n value: Record<string, unknown>,\n key: string,\n): Record<string, unknown> | null | undefined {\n const field = value[key];\n if (field === null) return null;\n return isRecord(field) ? field : undefined;\n}\n\nfunction stringOrNullField(\n value: Record<string, unknown>,\n key: string,\n): string | null | undefined {\n const field = value[key];\n if (field === null) return null;\n return typeof field === \"string\" ? field : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { markDatasetItemLabeled } from "../../src/lib/dataset-item-labeling.js";
1
2
  import { resolveLangfuseCreds } from "../../src/lib/langfuse-creds.js";
2
3
  import { DEMO_CONNECTION, addDemoDatasetItemFromTrace, copyDemoDatasetItem, isDemoMode } from "../../src/lib/langfuse-demo.js";
3
4
  import { formatLangfuseHttpError, formatLangfuseRequestError } from "../../src/lib/langfuse-errors.js";
@@ -353,18 +354,20 @@ function parseTrace(value) {
353
354
  function mergeKaizenMetadata(metadata, input) {
354
355
  const base = metadata ? { ...metadata } : {};
355
356
  const existingKaizen = isRecord(base.kaizen) ? base.kaizen : {};
356
- return {
357
+ const now = (/* @__PURE__ */ new Date()).toISOString();
358
+ const nextMetadata = {
357
359
  ...base,
358
360
  sourceTraceId: input.sourceTraceId ?? base.sourceTraceId,
359
361
  kaizen: {
360
362
  ...existingKaizen,
361
363
  notes: input.notes,
362
364
  sourceUrl: input.sourceUrl ?? existingKaizen.sourceUrl,
363
- lastEditedAt: (/* @__PURE__ */ new Date()).toISOString(),
365
+ lastEditedAt: now,
364
366
  lastEditedBy: "kaizen-studio",
365
367
  lastAction: input.action
366
368
  }
367
369
  };
370
+ return input.action === "updated" ? markDatasetItemLabeled(nextMetadata, now) : nextMetadata;
368
371
  }
369
372
  function getKaizenNotes(metadata) {
370
373
  if (!metadata) return "";
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-dataset-item.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset-item.ts"],"sourcesContent":["import { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport {\n addDemoDatasetItemFromTrace,\n copyDemoDatasetItem,\n DEMO_CONNECTION,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ninterface DatasetItem {\n id: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n status?: string | null;\n sourceTraceId?: string | null;\n}\n\ninterface Trace {\n id: string;\n input?: unknown;\n output?: unknown;\n metadata?: Record<string, unknown> | null;\n}\n\ntype MutationRequest =\n | {\n action: \"update\";\n datasetName: string;\n itemId: string;\n expectedOutput: unknown;\n notes: string;\n systemId?: string;\n }\n | {\n action: \"archive\";\n datasetName: string;\n itemId: string;\n systemId?: string;\n }\n | {\n action: \"add-from-trace\";\n datasetName: string;\n source: string;\n expectedOutput: unknown;\n notes: string;\n systemId?: string;\n }\n | {\n action: \"copy-item\";\n datasetName: string;\n input: unknown;\n expectedOutput: unknown;\n metadata: Record<string, unknown> | null;\n sourceTraceId: string | null;\n status: string | null;\n notes: string;\n systemId?: string;\n };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const request = parseMutationRequest(req.body);\n if (!request.ok) {\n res.status(400).json({ error: request.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n if (request.value.action === \"add-from-trace\") {\n const traceId = extractLangfuseTraceId(request.value.source);\n if (!traceId) {\n res\n .status(400)\n .json({ error: \"Could not extract trace ID from source\" });\n return;\n }\n const item = addDemoDatasetItemFromTrace(\n request.value.datasetName,\n traceId,\n {\n expectedOutput: request.value.expectedOutput,\n notes: request.value.notes,\n source: request.value.source,\n },\n );\n res.status(200).json({ data: item, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"copy-item\") {\n const item = copyDemoDatasetItem(request.value.datasetName, {\n input: request.value.input,\n expectedOutput: request.value.expectedOutput,\n metadata: request.value.metadata,\n sourceTraceId: request.value.sourceTraceId,\n status: request.value.status,\n });\n res.status(200).json({ data: item, connection: DEMO_CONNECTION });\n return;\n }\n res\n .status(200)\n .json({ data: null, success: true, connection: DEMO_CONNECTION });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n try {\n if (request.value.action === \"add-from-trace\") {\n const data = await addFromTrace(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"copy-item\") {\n const data = await copyItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"archive\") {\n const data = await archiveDatasetItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n const data = await updateDatasetItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function addFromTrace(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"add-from-trace\" }>,\n): Promise<DatasetItem> {\n const traceId = extractLangfuseTraceId(request.source);\n if (!traceId) {\n throw new LangfuseApiError(\n 400,\n \"Paste a Langfuse trace URL or trace ID. Orbit URL to trace lookup will be added separately.\",\n );\n }\n\n const trace = await getTrace(creds, traceId);\n // Langfuse rejects null input on dataset-item create. The item's input\n // mirrors the trace input; the user is expected to fill in the\n // expectedOutput as ground truth, so we keep it empty unless the\n // caller supplied one. The trace output is preserved under\n // metadata.kaizen.sourceOutput for reference.\n const baseMetadata = mergeKaizenMetadata(trace.metadata, {\n notes: request.notes,\n sourceTraceId: trace.id,\n sourceUrl: request.source.startsWith(\"http\") ? request.source : null,\n action: \"added\",\n });\n const metadataWithSourceOutput =\n trace.output === undefined || trace.output === null\n ? baseMetadata\n : {\n ...baseMetadata,\n kaizen: {\n ...(isRecord(baseMetadata.kaizen) ? baseMetadata.kaizen : {}),\n sourceOutput: trace.output,\n },\n };\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n input: trace.input ?? {},\n expectedOutput:\n request.expectedOutput === undefined ? {} : request.expectedOutput,\n metadata: metadataWithSourceOutput,\n sourceTraceId: trace.id,\n });\n}\n\nasync function copyItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"copy-item\" }>,\n): Promise<DatasetItem> {\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n input: request.input,\n expectedOutput: request.expectedOutput,\n metadata: request.metadata\n ? {\n ...request.metadata,\n kaizen: {\n ...(isRecord(request.metadata.kaizen)\n ? request.metadata.kaizen\n : {}),\n lastEditedAt: new Date().toISOString(),\n lastEditedBy: \"kaizen-studio\",\n lastAction: \"copied\",\n },\n }\n : null,\n sourceTraceId: request.sourceTraceId,\n status: request.status,\n });\n}\n\nasync function updateDatasetItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"update\" }>,\n): Promise<DatasetItem> {\n const item = await getDatasetItem(creds, request.itemId);\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: request.expectedOutput,\n metadata: mergeKaizenMetadata(item.metadata, {\n notes: request.notes,\n sourceTraceId: getSourceTraceId(item),\n sourceUrl: null,\n action: \"updated\",\n }),\n sourceTraceId: item.sourceTraceId,\n status: item.status,\n });\n}\n\nasync function archiveDatasetItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"archive\" }>,\n): Promise<DatasetItem> {\n const item = await getDatasetItem(creds, request.itemId);\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: mergeKaizenMetadata(item.metadata, {\n notes: getKaizenNotes(item.metadata),\n sourceTraceId: getSourceTraceId(item),\n sourceUrl: null,\n action: \"archived\",\n }),\n sourceTraceId: item.sourceTraceId,\n status: \"ARCHIVED\",\n });\n}\n\nasync function getDatasetItem(\n creds: LangfuseCreds,\n itemId: string,\n): Promise<DatasetItem> {\n const data = await langfuseJson(creds, \"the dataset item\", {\n path: `/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n method: \"GET\",\n });\n const item = parseDatasetItem(data);\n if (!item) {\n throw new LangfuseApiError(\n 502,\n \"Langfuse returned a dataset item without an id.\",\n );\n }\n return item;\n}\n\nasync function getTrace(creds: LangfuseCreds, traceId: string): Promise<Trace> {\n const data = await langfuseJson(creds, \"the trace\", {\n path: `/api/public/traces/${encodeURIComponent(traceId)}`,\n method: \"GET\",\n });\n const trace = parseTrace(data);\n if (!trace) {\n throw new LangfuseApiError(502, \"Langfuse returned a trace without an id.\");\n }\n return trace;\n}\n\nasync function upsertDatasetItem(\n creds: LangfuseCreds,\n item: {\n datasetName: string;\n id?: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n sourceTraceId?: string | null;\n status?: string | null;\n },\n): Promise<DatasetItem> {\n const body = {\n datasetName: item.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: item.metadata,\n sourceTraceId: item.sourceTraceId,\n status: item.status ?? undefined,\n };\n const data = await langfuseJson(creds, \"the dataset item\", {\n path: \"/api/public/dataset-items\",\n method: \"POST\",\n body,\n });\n const parsed = parseDatasetItem(data);\n if (!parsed) {\n throw new LangfuseApiError(\n 502,\n \"Langfuse updated the dataset item but returned an unexpected response.\",\n );\n }\n return parsed;\n}\n\nasync function langfuseJson(\n creds: LangfuseCreds,\n resource: string,\n request: {\n path: string;\n method: \"GET\" | \"POST\";\n body?: unknown;\n },\n): Promise<unknown> {\n // Only retry GETs — POSTs aren't safe to repeat (would double-insert).\n const maxAttempts = request.method === \"GET\" ? 3 : 1;\n let lastRes: Response | null = null;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${request.path}`, {\n method: request.method,\n headers: {\n Authorization: `Basic ${creds.auth}`,\n ...(request.body === undefined\n ? {}\n : { \"Content-Type\": \"application/json\" }),\n },\n body:\n request.body === undefined ? undefined : JSON.stringify(request.body),\n });\n if (res.ok) return res.json();\n lastRes = res;\n if (attempt < maxAttempts && isTransientGatewayStatus(res.status)) {\n await sleep(400 * 2 ** (attempt - 1));\n continue;\n }\n break;\n }\n throw new LangfuseApiError(\n lastRes!.status,\n await formatLangfuseHttpError(lastRes!, resource),\n );\n}\n\nfunction isTransientGatewayStatus(status: number): boolean {\n return status === 502 || status === 503 || status === 504 || status === 524;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n const datasetName = stringField(value, \"datasetName\");\n if (!datasetName) {\n return { ok: false, error: \"datasetName is required.\" };\n }\n\n const systemId = stringField(value, \"systemId\") ?? undefined;\n if (action === \"update\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n expectedOutput: value.expectedOutput,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n if (action === \"archive\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n systemId,\n },\n };\n }\n\n if (action === \"add-from-trace\") {\n const source = stringField(value, \"source\");\n if (!source) return { ok: false, error: \"source is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n source,\n expectedOutput: value.expectedOutput,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n if (action === \"copy-item\") {\n return {\n ok: true,\n value: {\n action,\n datasetName,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? (value.metadata as Record<string, unknown> | null)\n : null,\n sourceTraceId:\n typeof value.sourceTraceId === \"string\" ? value.sourceTraceId : null,\n status: typeof value.status === \"string\" ? value.status : null,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n return {\n ok: false,\n error: \"action must be update, archive, add-from-trace, or copy-item.\",\n };\n}\n\nexport function extractLangfuseTraceId(source: string): string | null {\n const trimmed = source.trim();\n if (!trimmed) return null;\n\n if (!looksLikeUrl(trimmed)) return trimmed;\n\n try {\n const url = new URL(trimmed);\n const traceId =\n url.searchParams.get(\"traceId\") ?? url.searchParams.get(\"trace\");\n if (traceId) return traceId;\n\n const parts = url.pathname.split(\"/\").filter(Boolean);\n const traceIndex = parts.findIndex(\n (part) => part === \"traces\" || part === \"trace\",\n );\n if (traceIndex >= 0) return parts[traceIndex + 1] ?? null;\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction parseDatasetItem(value: unknown): DatasetItem | null {\n if (!isRecord(value) || typeof value.id !== \"string\") return null;\n return {\n id: value.id,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? value.metadata\n : undefined,\n status:\n typeof value.status === \"string\" || value.status === null\n ? value.status\n : undefined,\n sourceTraceId:\n typeof value.sourceTraceId === \"string\" || value.sourceTraceId === null\n ? value.sourceTraceId\n : undefined,\n };\n}\n\nfunction parseTrace(value: unknown): Trace | null {\n if (!isRecord(value) || typeof value.id !== \"string\") return null;\n return {\n id: value.id,\n input: value.input,\n output: value.output,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? value.metadata\n : undefined,\n };\n}\n\nfunction mergeKaizenMetadata(\n metadata: Record<string, unknown> | null | undefined,\n input: {\n notes: string;\n sourceTraceId: string | null;\n sourceUrl: string | null;\n action: \"added\" | \"updated\" | \"archived\";\n },\n): Record<string, unknown> {\n const base = metadata ? { ...metadata } : {};\n const existingKaizen = isRecord(base.kaizen) ? base.kaizen : {};\n return {\n ...base,\n sourceTraceId: input.sourceTraceId ?? base.sourceTraceId,\n kaizen: {\n ...existingKaizen,\n notes: input.notes,\n sourceUrl: input.sourceUrl ?? existingKaizen.sourceUrl,\n lastEditedAt: new Date().toISOString(),\n lastEditedBy: \"kaizen-studio\",\n lastAction: input.action,\n },\n };\n}\n\nfunction getKaizenNotes(\n metadata: Record<string, unknown> | null | undefined,\n): string {\n if (!metadata) return \"\";\n if (isRecord(metadata.kaizen) && typeof metadata.kaizen.notes === \"string\") {\n return metadata.kaizen.notes;\n }\n return typeof metadata.notes === \"string\" ? metadata.notes : \"\";\n}\n\nfunction getSourceTraceId(item: DatasetItem): string | null {\n if (item.sourceTraceId) return item.sourceTraceId;\n const metadata = item.metadata;\n if (!metadata) return null;\n return typeof metadata.sourceTraceId === \"string\"\n ? metadata.sourceTraceId\n : null;\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction looksLikeUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-dataset-item.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset-item.ts"],"sourcesContent":["import { markDatasetItemLabeled } from \"../../src/lib/dataset-item-labeling\";\nimport { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport {\n addDemoDatasetItemFromTrace,\n copyDemoDatasetItem,\n DEMO_CONNECTION,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ninterface DatasetItem {\n id: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n status?: string | null;\n sourceTraceId?: string | null;\n}\n\ninterface Trace {\n id: string;\n input?: unknown;\n output?: unknown;\n metadata?: Record<string, unknown> | null;\n}\n\ntype MutationRequest =\n | {\n action: \"update\";\n datasetName: string;\n itemId: string;\n expectedOutput: unknown;\n notes: string;\n systemId?: string;\n }\n | {\n action: \"archive\";\n datasetName: string;\n itemId: string;\n systemId?: string;\n }\n | {\n action: \"add-from-trace\";\n datasetName: string;\n source: string;\n expectedOutput: unknown;\n notes: string;\n systemId?: string;\n }\n | {\n action: \"copy-item\";\n datasetName: string;\n input: unknown;\n expectedOutput: unknown;\n metadata: Record<string, unknown> | null;\n sourceTraceId: string | null;\n status: string | null;\n notes: string;\n systemId?: string;\n };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const request = parseMutationRequest(req.body);\n if (!request.ok) {\n res.status(400).json({ error: request.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n if (request.value.action === \"add-from-trace\") {\n const traceId = extractLangfuseTraceId(request.value.source);\n if (!traceId) {\n res\n .status(400)\n .json({ error: \"Could not extract trace ID from source\" });\n return;\n }\n const item = addDemoDatasetItemFromTrace(\n request.value.datasetName,\n traceId,\n {\n expectedOutput: request.value.expectedOutput,\n notes: request.value.notes,\n source: request.value.source,\n },\n );\n res.status(200).json({ data: item, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"copy-item\") {\n const item = copyDemoDatasetItem(request.value.datasetName, {\n input: request.value.input,\n expectedOutput: request.value.expectedOutput,\n metadata: request.value.metadata,\n sourceTraceId: request.value.sourceTraceId,\n status: request.value.status,\n });\n res.status(200).json({ data: item, connection: DEMO_CONNECTION });\n return;\n }\n res\n .status(200)\n .json({ data: null, success: true, connection: DEMO_CONNECTION });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n try {\n if (request.value.action === \"add-from-trace\") {\n const data = await addFromTrace(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"copy-item\") {\n const data = await copyItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"archive\") {\n const data = await archiveDatasetItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n return;\n }\n\n const data = await updateDatasetItem(creds, request.value);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ data });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function addFromTrace(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"add-from-trace\" }>,\n): Promise<DatasetItem> {\n const traceId = extractLangfuseTraceId(request.source);\n if (!traceId) {\n throw new LangfuseApiError(\n 400,\n \"Paste a Langfuse trace URL or trace ID. Orbit URL to trace lookup will be added separately.\",\n );\n }\n\n const trace = await getTrace(creds, traceId);\n // Langfuse rejects null input on dataset-item create. The item's input\n // mirrors the trace input; the user is expected to fill in the\n // expectedOutput as ground truth, so we keep it empty unless the\n // caller supplied one. The trace output is preserved under\n // metadata.kaizen.sourceOutput for reference.\n const baseMetadata = mergeKaizenMetadata(trace.metadata, {\n notes: request.notes,\n sourceTraceId: trace.id,\n sourceUrl: request.source.startsWith(\"http\") ? request.source : null,\n action: \"added\",\n });\n const metadataWithSourceOutput =\n trace.output === undefined || trace.output === null\n ? baseMetadata\n : {\n ...baseMetadata,\n kaizen: {\n ...(isRecord(baseMetadata.kaizen) ? baseMetadata.kaizen : {}),\n sourceOutput: trace.output,\n },\n };\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n input: trace.input ?? {},\n expectedOutput:\n request.expectedOutput === undefined ? {} : request.expectedOutput,\n metadata: metadataWithSourceOutput,\n sourceTraceId: trace.id,\n });\n}\n\nasync function copyItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"copy-item\" }>,\n): Promise<DatasetItem> {\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n input: request.input,\n expectedOutput: request.expectedOutput,\n metadata: request.metadata\n ? {\n ...request.metadata,\n kaizen: {\n ...(isRecord(request.metadata.kaizen)\n ? request.metadata.kaizen\n : {}),\n lastEditedAt: new Date().toISOString(),\n lastEditedBy: \"kaizen-studio\",\n lastAction: \"copied\",\n },\n }\n : null,\n sourceTraceId: request.sourceTraceId,\n status: request.status,\n });\n}\n\nasync function updateDatasetItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"update\" }>,\n): Promise<DatasetItem> {\n const item = await getDatasetItem(creds, request.itemId);\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: request.expectedOutput,\n metadata: mergeKaizenMetadata(item.metadata, {\n notes: request.notes,\n sourceTraceId: getSourceTraceId(item),\n sourceUrl: null,\n action: \"updated\",\n }),\n sourceTraceId: item.sourceTraceId,\n status: item.status,\n });\n}\n\nasync function archiveDatasetItem(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"archive\" }>,\n): Promise<DatasetItem> {\n const item = await getDatasetItem(creds, request.itemId);\n return upsertDatasetItem(creds, {\n datasetName: request.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: mergeKaizenMetadata(item.metadata, {\n notes: getKaizenNotes(item.metadata),\n sourceTraceId: getSourceTraceId(item),\n sourceUrl: null,\n action: \"archived\",\n }),\n sourceTraceId: item.sourceTraceId,\n status: \"ARCHIVED\",\n });\n}\n\nasync function getDatasetItem(\n creds: LangfuseCreds,\n itemId: string,\n): Promise<DatasetItem> {\n const data = await langfuseJson(creds, \"the dataset item\", {\n path: `/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n method: \"GET\",\n });\n const item = parseDatasetItem(data);\n if (!item) {\n throw new LangfuseApiError(\n 502,\n \"Langfuse returned a dataset item without an id.\",\n );\n }\n return item;\n}\n\nasync function getTrace(creds: LangfuseCreds, traceId: string): Promise<Trace> {\n const data = await langfuseJson(creds, \"the trace\", {\n path: `/api/public/traces/${encodeURIComponent(traceId)}`,\n method: \"GET\",\n });\n const trace = parseTrace(data);\n if (!trace) {\n throw new LangfuseApiError(502, \"Langfuse returned a trace without an id.\");\n }\n return trace;\n}\n\nasync function upsertDatasetItem(\n creds: LangfuseCreds,\n item: {\n datasetName: string;\n id?: string;\n input?: unknown;\n expectedOutput?: unknown;\n metadata?: Record<string, unknown> | null;\n sourceTraceId?: string | null;\n status?: string | null;\n },\n): Promise<DatasetItem> {\n const body = {\n datasetName: item.datasetName,\n id: item.id,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: item.metadata,\n sourceTraceId: item.sourceTraceId,\n status: item.status ?? undefined,\n };\n const data = await langfuseJson(creds, \"the dataset item\", {\n path: \"/api/public/dataset-items\",\n method: \"POST\",\n body,\n });\n const parsed = parseDatasetItem(data);\n if (!parsed) {\n throw new LangfuseApiError(\n 502,\n \"Langfuse updated the dataset item but returned an unexpected response.\",\n );\n }\n return parsed;\n}\n\nasync function langfuseJson(\n creds: LangfuseCreds,\n resource: string,\n request: {\n path: string;\n method: \"GET\" | \"POST\";\n body?: unknown;\n },\n): Promise<unknown> {\n // Only retry GETs — POSTs aren't safe to repeat (would double-insert).\n const maxAttempts = request.method === \"GET\" ? 3 : 1;\n let lastRes: Response | null = null;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const res = await fetch(`${creds.host.replace(/\\/$/, \"\")}${request.path}`, {\n method: request.method,\n headers: {\n Authorization: `Basic ${creds.auth}`,\n ...(request.body === undefined\n ? {}\n : { \"Content-Type\": \"application/json\" }),\n },\n body:\n request.body === undefined ? undefined : JSON.stringify(request.body),\n });\n if (res.ok) return res.json();\n lastRes = res;\n if (attempt < maxAttempts && isTransientGatewayStatus(res.status)) {\n await sleep(400 * 2 ** (attempt - 1));\n continue;\n }\n break;\n }\n throw new LangfuseApiError(\n lastRes!.status,\n await formatLangfuseHttpError(lastRes!, resource),\n );\n}\n\nfunction isTransientGatewayStatus(status: number): boolean {\n return status === 502 || status === 503 || status === 504 || status === 524;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n const datasetName = stringField(value, \"datasetName\");\n if (!datasetName) {\n return { ok: false, error: \"datasetName is required.\" };\n }\n\n const systemId = stringField(value, \"systemId\") ?? undefined;\n if (action === \"update\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n expectedOutput: value.expectedOutput,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n if (action === \"archive\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n itemId,\n systemId,\n },\n };\n }\n\n if (action === \"add-from-trace\") {\n const source = stringField(value, \"source\");\n if (!source) return { ok: false, error: \"source is required.\" };\n return {\n ok: true,\n value: {\n action,\n datasetName,\n source,\n expectedOutput: value.expectedOutput,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n if (action === \"copy-item\") {\n return {\n ok: true,\n value: {\n action,\n datasetName,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? (value.metadata as Record<string, unknown> | null)\n : null,\n sourceTraceId:\n typeof value.sourceTraceId === \"string\" ? value.sourceTraceId : null,\n status: typeof value.status === \"string\" ? value.status : null,\n notes: stringField(value, \"notes\") ?? \"\",\n systemId,\n },\n };\n }\n\n return {\n ok: false,\n error: \"action must be update, archive, add-from-trace, or copy-item.\",\n };\n}\n\nexport function extractLangfuseTraceId(source: string): string | null {\n const trimmed = source.trim();\n if (!trimmed) return null;\n\n if (!looksLikeUrl(trimmed)) return trimmed;\n\n try {\n const url = new URL(trimmed);\n const traceId =\n url.searchParams.get(\"traceId\") ?? url.searchParams.get(\"trace\");\n if (traceId) return traceId;\n\n const parts = url.pathname.split(\"/\").filter(Boolean);\n const traceIndex = parts.findIndex(\n (part) => part === \"traces\" || part === \"trace\",\n );\n if (traceIndex >= 0) return parts[traceIndex + 1] ?? null;\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction parseDatasetItem(value: unknown): DatasetItem | null {\n if (!isRecord(value) || typeof value.id !== \"string\") return null;\n return {\n id: value.id,\n input: value.input,\n expectedOutput: value.expectedOutput,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? value.metadata\n : undefined,\n status:\n typeof value.status === \"string\" || value.status === null\n ? value.status\n : undefined,\n sourceTraceId:\n typeof value.sourceTraceId === \"string\" || value.sourceTraceId === null\n ? value.sourceTraceId\n : undefined,\n };\n}\n\nfunction parseTrace(value: unknown): Trace | null {\n if (!isRecord(value) || typeof value.id !== \"string\") return null;\n return {\n id: value.id,\n input: value.input,\n output: value.output,\n metadata:\n isRecord(value.metadata) || value.metadata === null\n ? value.metadata\n : undefined,\n };\n}\n\nexport function mergeKaizenMetadata(\n metadata: Record<string, unknown> | null | undefined,\n input: {\n notes: string;\n sourceTraceId: string | null;\n sourceUrl: string | null;\n action: \"added\" | \"updated\" | \"archived\";\n },\n): Record<string, unknown> {\n const base = metadata ? { ...metadata } : {};\n const existingKaizen = isRecord(base.kaizen) ? base.kaizen : {};\n const now = new Date().toISOString();\n const nextMetadata = {\n ...base,\n sourceTraceId: input.sourceTraceId ?? base.sourceTraceId,\n kaizen: {\n ...existingKaizen,\n notes: input.notes,\n sourceUrl: input.sourceUrl ?? existingKaizen.sourceUrl,\n lastEditedAt: now,\n lastEditedBy: \"kaizen-studio\",\n lastAction: input.action,\n },\n };\n return input.action === \"updated\"\n ? markDatasetItemLabeled(nextMetadata, now)\n : nextMetadata;\n}\n\nfunction getKaizenNotes(\n metadata: Record<string, unknown> | null | undefined,\n): string {\n if (!metadata) return \"\";\n if (isRecord(metadata.kaizen) && typeof metadata.kaizen.notes === \"string\") {\n return metadata.kaizen.notes;\n }\n return typeof metadata.notes === \"string\" ? metadata.notes : \"\";\n}\n\nfunction getSourceTraceId(item: DatasetItem): string | null {\n if (item.sourceTraceId) return item.sourceTraceId;\n const metadata = item.metadata;\n if (!metadata) return null;\n return typeof metadata.sourceTraceId === \"string\"\n ? metadata.sourceTraceId\n : null;\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction looksLikeUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
+ import { getSourceTraceId, isRecord } from "../../src/lib/langfuse-helpers.js";
1
2
  import { resolveLangfuseCreds } from "../../src/lib/langfuse-creds.js";
2
3
  import { DEMO_CONNECTION, createDemoDataset, deleteDemoDatasetItem, deleteDemoDatasetItems, isDemoMode, renameDemoDataset } from "../../src/lib/langfuse-demo.js";
3
4
  import { formatLangfuseHttpError, formatLangfuseRequestError } from "../../src/lib/langfuse-errors.js";
4
- import { getSourceTraceId, isRecord } from "../../src/lib/langfuse-helpers.js";
5
5
  import { datasetItemsCache } from "./langfuse-dataset.js";
6
6
  import { membershipsCache } from "./langfuse-trace-memberships.js";
7
7
  //#region dashboard/pages/api/langfuse-dataset-mutation.ts
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-dataset-mutation.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset-mutation.ts"],"sourcesContent":["import { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport {\n createDemoDataset,\n DEMO_CONNECTION,\n deleteDemoDatasetItem,\n deleteDemoDatasetItems,\n isDemoMode,\n renameDemoDataset,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { getSourceTraceId, isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ntype MutationRequest =\n | { action: \"create\"; name: string; description?: string }\n | { action: \"delete\"; name: string }\n | { action: \"delete-item\"; itemId: string }\n | { action: \"rename\"; oldName: string; newName: string };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const request = parseMutationRequest(req.body);\n if (!request.ok) {\n res.status(400).json({ error: request.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n if (request.value.action === \"create\") {\n const ds = createDemoDataset(\n request.value.name,\n request.value.description,\n );\n res.status(200).json({ data: ds, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"delete\") {\n deleteDemoDatasetItems(request.value.name);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"delete-item\") {\n deleteDemoDatasetItem(request.value.itemId);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"rename\") {\n renameDemoDataset(request.value.oldName, request.value.newName);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const lfCreds: LangfuseCreds = { host: creds.host, auth: creds.auth };\n\n try {\n if (request.value.action === \"create\") {\n const data = await createDataset(lfCreds, request.value);\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"delete\") {\n await deleteDatasetItems(lfCreds, request.value.name);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ success: true });\n return;\n }\n\n if (request.value.action === \"delete-item\") {\n await deleteDatasetItem(lfCreds, request.value.itemId);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ success: true });\n return;\n }\n\n if (request.value.action === \"rename\") {\n await renameDataset(\n lfCreds,\n request.value.oldName,\n request.value.newName,\n );\n res.status(200).json({ success: true });\n return;\n }\n\n res.status(400).json({ error: \"Unknown action\" });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function createDataset(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"create\" }>,\n): Promise<unknown> {\n const base = creds.host.replace(/\\/$/, \"\");\n const body: Record<string, unknown> = { name: request.name };\n if (request.description) body.description = request.description;\n\n const lfRes = await fetch(`${base}/api/public/v2/datasets`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!lfRes.ok) {\n throw new LangfuseApiError(\n lfRes.status,\n await formatLangfuseHttpError(lfRes, \"datasets\"),\n );\n }\n return lfRes.json();\n}\n\nasync function deleteDatasetItems(\n creds: LangfuseCreds,\n datasetName: string,\n signal?: AbortSignal,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const limit = 100;\n const maxRounds = 50;\n\n for (let round = 0; round < maxRounds; round++) {\n const listRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(datasetName)}&limit=${limit}&page=1`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal,\n },\n );\n if (!listRes.ok) {\n throw new LangfuseApiError(\n listRes.status,\n await formatLangfuseHttpError(listRes, \"dataset items\"),\n );\n }\n const body = await listRes.json();\n const items = body.data ?? [];\n if (!Array.isArray(items) || items.length === 0) break;\n\n let deleted = 0;\n for (const item of items) {\n if (typeof item.id === \"string\") {\n await deleteDatasetItem(creds, item.id, signal);\n deleted++;\n }\n }\n\n if (deleted === 0) break;\n if (items.length < limit) break;\n }\n}\n\nasync function deleteDatasetItem(\n creds: LangfuseCreds,\n itemId: string,\n signal?: AbortSignal,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const lfRes = await fetch(\n `${base}/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n {\n method: \"DELETE\",\n headers: { Authorization: `Basic ${creds.auth}` },\n signal,\n },\n );\n if (!lfRes.ok && lfRes.status !== 404) {\n throw new LangfuseApiError(\n lfRes.status,\n await formatLangfuseHttpError(lfRes, \"the dataset item\"),\n );\n }\n}\n\nasync function renameDataset(\n creds: LangfuseCreds,\n oldName: string,\n newName: string,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 120_000);\n const fetchOpts = {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n };\n\n let deletingOld = false;\n try {\n // 1. Fetch all items from the old dataset\n const allItems: Array<Record<string, unknown>> = [];\n let currentPage = 1;\n const limit = 100;\n while (true) {\n const listRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(oldName)}&limit=${limit}&page=${currentPage}`,\n fetchOpts,\n );\n if (!listRes.ok) {\n throw new LangfuseApiError(\n listRes.status,\n await formatLangfuseHttpError(listRes, \"dataset items\"),\n );\n }\n const body = await listRes.json();\n const items = body.data ?? [];\n if (!Array.isArray(items)) break;\n for (const item of items) {\n if (isRecord(item)) allItems.push(item);\n }\n if (items.length < limit) break;\n currentPage++;\n }\n\n // 2. Verify the target name doesn't already exist (Langfuse create is\n // an upsert, so we must guard against overwriting an existing dataset).\n const checkRes = await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(newName)}`,\n fetchOpts,\n );\n if (checkRes.ok) {\n throw new LangfuseApiError(\n 409,\n `A dataset named \"${newName}\" already exists.`,\n );\n }\n if (checkRes.status !== 404) {\n throw new LangfuseApiError(\n checkRes.status,\n await formatLangfuseHttpError(checkRes, \"dataset existence check\"),\n );\n }\n\n // 3. Create the new dataset\n const createRes = await fetch(`${base}/api/public/v2/datasets`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n signal: controller.signal,\n body: JSON.stringify({ name: newName }),\n });\n if (!createRes.ok) {\n throw new LangfuseApiError(\n createRes.status,\n await formatLangfuseHttpError(createRes, \"datasets\"),\n );\n }\n\n // 3. Copy each item to the new dataset\n for (const item of allItems) {\n const upsertRes = await fetch(`${base}/api/public/dataset-items`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n signal: controller.signal,\n body: JSON.stringify({\n datasetName: newName,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: {\n ...(isRecord(item.metadata) ? item.metadata : {}),\n kaizen: {\n ...(isRecord(item.metadata) && isRecord(item.metadata.kaizen)\n ? item.metadata.kaizen\n : {}),\n lastEditedAt: new Date().toISOString(),\n lastEditedBy: \"kaizen-studio\",\n lastAction: \"copied\",\n },\n },\n sourceTraceId: getSourceTraceId(item),\n status: typeof item.status === \"string\" ? item.status : undefined,\n }),\n });\n if (!upsertRes.ok) {\n throw new LangfuseApiError(\n upsertRes.status,\n await formatLangfuseHttpError(upsertRes, \"the dataset item\"),\n );\n }\n }\n\n // 4. Delete all items from the old dataset, then delete the dataset itself\n deletingOld = true;\n await deleteDatasetItems(creds, oldName, controller.signal);\n // Best-effort: the rename is functionally complete once items are copied\n // and old items deleted. Ignore empty shell deletion status because\n // throwing would report an error for a successful rename and\n // retrying would fail with 409 since the new dataset already exists.\n const deleteDatasetRes = await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(oldName)}`,\n {\n method: \"DELETE\",\n ...fetchOpts,\n },\n );\n await deleteDatasetRes.arrayBuffer().catch(() => undefined);\n } catch (err) {\n // Only clean up the new dataset if the error happened before step 4.\n // Once we start deleting the old dataset, the new one is the only\n // complete copy and must be preserved.\n if (!deletingOld) {\n try {\n const cleanupController = new AbortController();\n const cleanupTimeout = setTimeout(\n () => cleanupController.abort(),\n 30_000,\n );\n const cleanupOpts = {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: cleanupController.signal,\n };\n try {\n await deleteDatasetItems(creds, newName, cleanupController.signal);\n await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(newName)}`,\n { method: \"DELETE\", ...cleanupOpts },\n );\n } finally {\n clearTimeout(cleanupTimeout);\n }\n } catch {\n // Cleanup is best-effort; ignore failures.\n }\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n // Always clear caches — even on partial failure, some items may have been\n // copied or deleted and the old cached state is no longer reliable.\n datasetItemsCache.clear();\n membershipsCache.clear();\n }\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n\n if (action === \"create\") {\n const name = stringField(value, \"name\");\n if (!name) return { ok: false, error: \"name is required.\" };\n return {\n ok: true,\n value: {\n action,\n name,\n description: stringField(value, \"description\") ?? undefined,\n },\n };\n }\n\n if (action === \"delete\") {\n const name = stringField(value, \"name\");\n if (!name) return { ok: false, error: \"name is required.\" };\n return { ok: true, value: { action, name } };\n }\n\n if (action === \"delete-item\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return { ok: true, value: { action, itemId } };\n }\n\n if (action === \"rename\") {\n const oldName = stringField(value, \"oldName\");\n const newName = stringField(value, \"newName\");\n if (!oldName || !newName)\n return { ok: false, error: \"oldName and newName are required.\" };\n if (oldName === newName)\n return { ok: false, error: \"oldName and newName must be different.\" };\n return { ok: true, value: { action, oldName, newName } };\n }\n\n return {\n ok: false,\n error: \"action must be create, delete, delete-item, or rename.\",\n };\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-dataset-mutation.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset-mutation.ts"],"sourcesContent":["import { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport {\n createDemoDataset,\n DEMO_CONNECTION,\n deleteDemoDatasetItem,\n deleteDemoDatasetItems,\n isDemoMode,\n renameDemoDataset,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { getSourceTraceId, isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\nimport { datasetItemsCache } from \"./langfuse-dataset\";\nimport { membershipsCache } from \"./langfuse-trace-memberships\";\n\ninterface LangfuseCreds {\n host: string;\n auth: string;\n}\n\ntype MutationRequest =\n | { action: \"create\"; name: string; description?: string }\n | { action: \"delete\"; name: string }\n | { action: \"delete-item\"; itemId: string }\n | { action: \"rename\"; oldName: string; newName: string };\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n if (req.method !== \"POST\") {\n res.status(405).json({ error: \"POST only\" });\n return;\n }\n\n const request = parseMutationRequest(req.body);\n if (!request.ok) {\n res.status(400).json({ error: request.error });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n if (request.value.action === \"create\") {\n const ds = createDemoDataset(\n request.value.name,\n request.value.description,\n );\n res.status(200).json({ data: ds, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"delete\") {\n deleteDemoDatasetItems(request.value.name);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"delete-item\") {\n deleteDemoDatasetItem(request.value.itemId);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n if (request.value.action === \"rename\") {\n renameDemoDataset(request.value.oldName, request.value.newName);\n res.status(200).json({ success: true, connection: DEMO_CONNECTION });\n return;\n }\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const lfCreds: LangfuseCreds = { host: creds.host, auth: creds.auth };\n\n try {\n if (request.value.action === \"create\") {\n const data = await createDataset(lfCreds, request.value);\n res.status(200).json({ data });\n return;\n }\n\n if (request.value.action === \"delete\") {\n await deleteDatasetItems(lfCreds, request.value.name);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ success: true });\n return;\n }\n\n if (request.value.action === \"delete-item\") {\n await deleteDatasetItem(lfCreds, request.value.itemId);\n datasetItemsCache.clear();\n membershipsCache.clear();\n res.status(200).json({ success: true });\n return;\n }\n\n if (request.value.action === \"rename\") {\n await renameDataset(\n lfCreds,\n request.value.oldName,\n request.value.newName,\n );\n res.status(200).json({ success: true });\n return;\n }\n\n res.status(400).json({ error: \"Unknown action\" });\n } catch (err) {\n const status = err instanceof LangfuseApiError ? err.status : 502;\n const error =\n err instanceof LangfuseApiError\n ? err.message\n : formatLangfuseRequestError(err);\n res.status(status).json({ error });\n }\n}\n\nasync function createDataset(\n creds: LangfuseCreds,\n request: Extract<MutationRequest, { action: \"create\" }>,\n): Promise<unknown> {\n const base = creds.host.replace(/\\/$/, \"\");\n const body: Record<string, unknown> = { name: request.name };\n if (request.description) body.description = request.description;\n\n const lfRes = await fetch(`${base}/api/public/v2/datasets`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!lfRes.ok) {\n throw new LangfuseApiError(\n lfRes.status,\n await formatLangfuseHttpError(lfRes, \"datasets\"),\n );\n }\n return lfRes.json();\n}\n\nasync function deleteDatasetItems(\n creds: LangfuseCreds,\n datasetName: string,\n signal?: AbortSignal,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const limit = 100;\n const maxRounds = 50;\n\n for (let round = 0; round < maxRounds; round++) {\n const listRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(datasetName)}&limit=${limit}&page=1`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal,\n },\n );\n if (!listRes.ok) {\n throw new LangfuseApiError(\n listRes.status,\n await formatLangfuseHttpError(listRes, \"dataset items\"),\n );\n }\n const body = await listRes.json();\n const items = body.data ?? [];\n if (!Array.isArray(items) || items.length === 0) break;\n\n let deleted = 0;\n for (const item of items) {\n if (typeof item.id === \"string\") {\n await deleteDatasetItem(creds, item.id, signal);\n deleted++;\n }\n }\n\n if (deleted === 0) break;\n if (items.length < limit) break;\n }\n}\n\nasync function deleteDatasetItem(\n creds: LangfuseCreds,\n itemId: string,\n signal?: AbortSignal,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const lfRes = await fetch(\n `${base}/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n {\n method: \"DELETE\",\n headers: { Authorization: `Basic ${creds.auth}` },\n signal,\n },\n );\n if (!lfRes.ok && lfRes.status !== 404) {\n throw new LangfuseApiError(\n lfRes.status,\n await formatLangfuseHttpError(lfRes, \"the dataset item\"),\n );\n }\n}\n\nasync function renameDataset(\n creds: LangfuseCreds,\n oldName: string,\n newName: string,\n): Promise<void> {\n const base = creds.host.replace(/\\/$/, \"\");\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 120_000);\n const fetchOpts = {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n };\n\n let deletingOld = false;\n try {\n // 1. Fetch all items from the old dataset\n const allItems: Array<Record<string, unknown>> = [];\n let currentPage = 1;\n const limit = 100;\n while (true) {\n const listRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(oldName)}&limit=${limit}&page=${currentPage}`,\n fetchOpts,\n );\n if (!listRes.ok) {\n throw new LangfuseApiError(\n listRes.status,\n await formatLangfuseHttpError(listRes, \"dataset items\"),\n );\n }\n const body = await listRes.json();\n const items = body.data ?? [];\n if (!Array.isArray(items)) break;\n for (const item of items) {\n if (isRecord(item)) allItems.push(item);\n }\n if (items.length < limit) break;\n currentPage++;\n }\n\n // 2. Verify the target name doesn't already exist (Langfuse create is\n // an upsert, so we must guard against overwriting an existing dataset).\n const checkRes = await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(newName)}`,\n fetchOpts,\n );\n if (checkRes.ok) {\n throw new LangfuseApiError(\n 409,\n `A dataset named \"${newName}\" already exists.`,\n );\n }\n if (checkRes.status !== 404) {\n throw new LangfuseApiError(\n checkRes.status,\n await formatLangfuseHttpError(checkRes, \"dataset existence check\"),\n );\n }\n\n // 3. Create the new dataset\n const createRes = await fetch(`${base}/api/public/v2/datasets`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n signal: controller.signal,\n body: JSON.stringify({ name: newName }),\n });\n if (!createRes.ok) {\n throw new LangfuseApiError(\n createRes.status,\n await formatLangfuseHttpError(createRes, \"datasets\"),\n );\n }\n\n // 3. Copy each item to the new dataset\n for (const item of allItems) {\n const upsertRes = await fetch(`${base}/api/public/dataset-items`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${creds.auth}`,\n \"Content-Type\": \"application/json\",\n },\n signal: controller.signal,\n body: JSON.stringify({\n datasetName: newName,\n input: item.input,\n expectedOutput: item.expectedOutput,\n metadata: {\n ...(isRecord(item.metadata) ? item.metadata : {}),\n kaizen: {\n ...(isRecord(item.metadata) && isRecord(item.metadata.kaizen)\n ? item.metadata.kaizen\n : {}),\n lastEditedAt: new Date().toISOString(),\n lastEditedBy: \"kaizen-studio\",\n lastAction: \"copied\",\n },\n },\n sourceTraceId: getSourceTraceId(item),\n status: typeof item.status === \"string\" ? item.status : undefined,\n }),\n });\n if (!upsertRes.ok) {\n throw new LangfuseApiError(\n upsertRes.status,\n await formatLangfuseHttpError(upsertRes, \"the dataset item\"),\n );\n }\n }\n\n // 4. Delete all items from the old dataset, then delete the dataset itself\n deletingOld = true;\n await deleteDatasetItems(creds, oldName, controller.signal);\n // Best-effort: the rename is functionally complete once items are copied\n // and old items deleted. Ignore empty shell deletion status because\n // throwing would report an error for a successful rename and\n // retrying would fail with 409 since the new dataset already exists.\n const deleteDatasetRes = await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(oldName)}`,\n {\n method: \"DELETE\",\n ...fetchOpts,\n },\n );\n await deleteDatasetRes.arrayBuffer().catch(() => undefined);\n } catch (err) {\n // Only clean up the new dataset if the error happened before step 4.\n // Once we start deleting the old dataset, the new one is the only\n // complete copy and must be preserved.\n if (!deletingOld) {\n try {\n const cleanupController = new AbortController();\n const cleanupTimeout = setTimeout(\n () => cleanupController.abort(),\n 30_000,\n );\n const cleanupOpts = {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: cleanupController.signal,\n };\n try {\n await deleteDatasetItems(creds, newName, cleanupController.signal);\n await fetch(\n `${base}/api/public/v2/datasets/${encodeURIComponent(newName)}`,\n { method: \"DELETE\", ...cleanupOpts },\n );\n } finally {\n clearTimeout(cleanupTimeout);\n }\n } catch {\n // Cleanup is best-effort; ignore failures.\n }\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n // Always clear caches — even on partial failure, some items may have been\n // copied or deleted and the old cached state is no longer reliable.\n datasetItemsCache.clear();\n membershipsCache.clear();\n }\n}\n\nfunction parseMutationRequest(\n value: unknown,\n): { ok: true; value: MutationRequest } | { ok: false; error: string } {\n if (!isRecord(value)) {\n return { ok: false, error: \"Request body must be an object.\" };\n }\n\n const action = stringField(value, \"action\");\n\n if (action === \"create\") {\n const name = stringField(value, \"name\");\n if (!name) return { ok: false, error: \"name is required.\" };\n return {\n ok: true,\n value: {\n action,\n name,\n description: stringField(value, \"description\") ?? undefined,\n },\n };\n }\n\n if (action === \"delete\") {\n const name = stringField(value, \"name\");\n if (!name) return { ok: false, error: \"name is required.\" };\n return { ok: true, value: { action, name } };\n }\n\n if (action === \"delete-item\") {\n const itemId = stringField(value, \"itemId\");\n if (!itemId) return { ok: false, error: \"itemId is required.\" };\n return { ok: true, value: { action, itemId } };\n }\n\n if (action === \"rename\") {\n const oldName = stringField(value, \"oldName\");\n const newName = stringField(value, \"newName\");\n if (!oldName || !newName)\n return { ok: false, error: \"oldName and newName are required.\" };\n if (oldName === newName)\n return { ok: false, error: \"oldName and newName must be different.\" };\n return { ok: true, value: { action, oldName, newName } };\n }\n\n return {\n ok: false,\n error: \"action must be create, delete, delete-item, or rename.\",\n };\n}\n\nfunction stringField(\n record: Record<string, unknown>,\n key: string,\n): string | null {\n const value = record[key];\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-dataset.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasetItems,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport const datasetItemsCache = createTtlCache({ ttlMs: 15_000 });\nconst cache = datasetItemsCache;\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { dataset, page, noCache, itemId } = req.query;\n if (typeof dataset !== \"string\" || dataset === \"\") {\n res.status(400).json({ error: \"dataset query param required\" });\n return;\n }\n\n const pageNum = typeof page === \"string\" ? Number.parseInt(page, 10) : null;\n if (pageNum !== null && (!Number.isInteger(pageNum) || pageNum < 1)) {\n res\n .status(400)\n .json({ error: \"page query param must be a positive integer\" });\n return;\n }\n\n // Always paginate — default to page 1 when no page param is provided.\n const effectivePage = pageNum ?? 1;\n const limit = 50;\n\n const creds = resolveLangfuseCreds();\n const hasLangfuseCredentials = Boolean(creds.host && creds.auth);\n if (!hasLangfuseCredentials) {\n if (isDemoMode()) {\n const allItems: unknown[] = getDemoDatasetItems(dataset);\n if (typeof itemId === \"string\" && itemId !== \"\") {\n const found = allItems.find(\n (i) =>\n typeof i === \"object\" &&\n i !== null &&\n (i as Record<string, unknown>).id === itemId,\n );\n res.status(200).json({\n data: found ? [found] : [],\n meta: { page: 1, totalPages: 1, totalItems: found ? 1 : 0 },\n connection: DEMO_CONNECTION,\n });\n return;\n }\n const start = (effectivePage - 1) * limit;\n const pageItems = allItems.slice(start, start + limit);\n const totalPages = Math.max(1, Math.ceil(allItems.length / limit));\n res.status(200).json({\n data: pageItems,\n meta: {\n page: effectivePage,\n totalPages,\n totalItems: allItems.length,\n },\n connection: DEMO_CONNECTION,\n });\n return;\n }\n res.status(200).json({ data: [], connection: creds.connection });\n return;\n }\n\n // Fetch a single item by ID (for item-detail view deep links).\n if (typeof itemId === \"string\" && itemId !== \"\") {\n const itemCacheKey = `ds-item:${itemId}`;\n const skipItemCache = noCache === \"1\";\n const cachedItem = !skipItemCache ? cache.get(itemCacheKey) : undefined;\n if (cachedItem !== undefined) {\n res.status(200).json(cachedItem);\n return;\n }\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n const detail = await formatLangfuseHttpError(lfRes, \"dataset item\");\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n const item = await lfRes.json();\n const body = {\n data: [item],\n meta: { page: 1, totalPages: 1, totalItems: 1 },\n connection: creds.connection,\n };\n cache.set(itemCacheKey, body);\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n return;\n }\n\n const skipCache = noCache === \"1\";\n const cacheKey = `ds:${dataset}:${effectivePage}`;\n const cached = !skipCache ? cache.get(cacheKey) : undefined;\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/dataset-items?datasetName=${encodeURIComponent(dataset)}&limit=${limit}&page=${effectivePage}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n const detail = await formatLangfuseHttpError(lfRes, \"dataset items\");\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n const data = await lfRes.json();\n const items: unknown[] = data.data ?? [];\n const meta = data.meta ?? {};\n const totalPages =\n typeof meta.totalPages === \"number\"\n ? meta.totalPages\n : typeof meta.total_pages === \"number\"\n ? meta.total_pages\n : items.length < limit\n ? effectivePage\n : effectivePage + 1;\n\n const body = {\n data: items,\n meta: {\n page: effectivePage,\n totalPages,\n totalItems: meta.totalItems ?? meta.total_items,\n },\n connection: creds.connection,\n };\n cache.set(cacheKey, body);\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-dataset.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-dataset.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasetItems,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport const datasetItemsCache = createTtlCache({ ttlMs: 15_000 });\nconst cache = datasetItemsCache;\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { dataset, page, noCache, itemId } = req.query;\n if (typeof dataset !== \"string\" || dataset === \"\") {\n res.status(400).json({ error: \"dataset query param required\" });\n return;\n }\n\n const pageNum = typeof page === \"string\" ? Number.parseInt(page, 10) : null;\n if (pageNum !== null && (!Number.isInteger(pageNum) || pageNum < 1)) {\n res\n .status(400)\n .json({ error: \"page query param must be a positive integer\" });\n return;\n }\n\n // Always paginate — default to page 1 when no page param is provided.\n const effectivePage = pageNum ?? 1;\n const limit = 50;\n\n const creds = resolveLangfuseCreds();\n const hasLangfuseCredentials = Boolean(creds.host && creds.auth);\n if (!hasLangfuseCredentials) {\n if (isDemoMode()) {\n const allItems: unknown[] = getDemoDatasetItems(dataset);\n if (typeof itemId === \"string\" && itemId !== \"\") {\n const found = allItems.find(\n (i) =>\n typeof i === \"object\" &&\n i !== null &&\n (i as Record<string, unknown>).id === itemId,\n );\n res.status(200).json({\n data: found ? [found] : [],\n meta: { page: 1, totalPages: 1, totalItems: found ? 1 : 0 },\n connection: DEMO_CONNECTION,\n });\n return;\n }\n const start = (effectivePage - 1) * limit;\n const pageItems = allItems.slice(start, start + limit);\n const totalPages = Math.max(1, Math.ceil(allItems.length / limit));\n res.status(200).json({\n data: pageItems,\n meta: {\n page: effectivePage,\n totalPages,\n totalItems: allItems.length,\n },\n connection: DEMO_CONNECTION,\n });\n return;\n }\n res.status(200).json({ data: [], connection: creds.connection });\n return;\n }\n\n // Fetch a single item by ID (for item-detail view deep links).\n if (typeof itemId === \"string\" && itemId !== \"\") {\n const itemCacheKey = `ds-item:${itemId}`;\n const skipItemCache = noCache === \"1\";\n const cachedItem = !skipItemCache ? cache.get(itemCacheKey) : undefined;\n if (cachedItem !== undefined) {\n res.status(200).json(cachedItem);\n return;\n }\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/dataset-items/${encodeURIComponent(itemId)}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n const detail = await formatLangfuseHttpError(lfRes, \"dataset item\");\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n const item = await lfRes.json();\n const body = {\n data: [item],\n meta: { page: 1, totalPages: 1, totalItems: 1 },\n connection: creds.connection,\n };\n cache.set(itemCacheKey, body);\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n return;\n }\n\n const skipCache = noCache === \"1\";\n const cacheKey = `ds:${dataset}:${effectivePage}`;\n const cached = !skipCache ? cache.get(cacheKey) : undefined;\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/dataset-items?datasetName=${encodeURIComponent(dataset)}&limit=${limit}&page=${effectivePage}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n const detail = await formatLangfuseHttpError(lfRes, \"dataset items\");\n res.status(lfRes.status).json({\n error: detail,\n connection: langfuseStatus(\n lfRes.status === 401 || lfRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n const data = await lfRes.json();\n const items: unknown[] = data.data ?? [];\n const meta = data.meta ?? {};\n const totalPages =\n typeof meta.totalPages === \"number\"\n ? meta.totalPages\n : typeof meta.total_pages === \"number\"\n ? meta.total_pages\n : items.length < limit\n ? effectivePage\n : effectivePage + 1;\n\n const body = {\n data: items,\n meta: {\n page: effectivePage,\n totalPages,\n totalItems: meta.totalItems ?? meta.total_items,\n },\n connection: creds.connection,\n };\n cache.set(cacheKey, body);\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-datasets.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-datasets.ts"],"sourcesContent":["import {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasets,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default async function handler(\n _req: StudioRequest,\n res: StudioResponse,\n) {\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n res\n .status(200)\n .json({ data: getDemoDatasets(), connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ data: [], connection: creds.connection });\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const datasets = await fetchDatasets(creds.host, creds.auth, controller);\n res.status(200).json({ data: datasets, connection: creds.connection });\n } catch (err) {\n const detail =\n err instanceof Error && err.message.startsWith(\"Kaizen could not load\")\n ? err.message\n : formatLangfuseRequestError(err);\n const state =\n err instanceof LangfuseApiError &&\n (err.status === 401 || err.status === 403)\n ? \"auth_failed\"\n : err instanceof LangfuseApiError\n ? \"query_failed\"\n : \"network_error\";\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(state, { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nasync function fetchDatasets(\n host: string,\n auth: string,\n controller: AbortController,\n): Promise<unknown[]> {\n const base = host.replace(/\\/$/, \"\");\n const limit = 100;\n const out: unknown[] = [];\n let page = 1;\n\n while (true) {\n const res = await fetch(\n `${base}/api/public/v2/datasets?limit=${limit}&page=${page}`,\n {\n headers: { Authorization: `Basic ${auth}` },\n signal: controller.signal,\n },\n );\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, \"datasets\"),\n );\n }\n const body = await res.json();\n const items = Array.isArray(body) ? body : (body.data ?? []);\n out.push(...items);\n\n const totalPages = Number(body.meta?.totalPages ?? body.meta?.total_pages);\n if (Number.isFinite(totalPages) && page >= totalPages) break;\n if (!Array.isArray(items) || items.length < limit) break;\n page++;\n }\n\n return out;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":";;;;AAkBA,eAA8B,QAC5B,MACA,KACA;CACA,MAAM,QAAQ,sBAAsB;AACpC,KAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,MAAM;AAC9B,MAAI,YAAY,EAAE;AAChB,OACG,OAAO,IAAI,CACX,KAAK;IAAE,MAAM,iBAAiB;IAAE,YAAY;IAAiB,CAAC;AACjE;;AAEF,MAAI,OAAO,IAAI,CAAC,KAAK;GAAE,MAAM,EAAE;GAAE,YAAY,MAAM;GAAY,CAAC;AAChE;;CAGF,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,UAAU,iBAAiB,WAAW,OAAO,EAAE,IAAO;AAC5D,KAAI;EACF,MAAM,WAAW,MAAM,cAAc,MAAM,MAAM,MAAM,MAAM,WAAW;AACxE,MAAI,OAAO,IAAI,CAAC,KAAK;GAAE,MAAM;GAAU,YAAY,MAAM;GAAY,CAAC;UAC/D,KAAK;EACZ,MAAM,SACJ,eAAe,SAAS,IAAI,QAAQ,WAAW,wBAAwB,GACnE,IAAI,UACJ,2BAA2B,IAAI;EACrC,MAAM,QACJ,eAAe,qBACd,IAAI,WAAW,OAAO,IAAI,WAAW,OAClC,gBACA,eAAe,mBACb,iBACA;AACR,MAAI,OAAO,IAAI,CAAC,KAAK;GACnB,OAAO;GACP,YAAY,eAAe,OAAO,EAAE,QAAQ,CAAC;GAC9C,CAAC;WACM;AACR,eAAa,QAAQ;;;AAIzB,eAAe,cACb,MACA,MACA,YACoB;CACpB,MAAM,OAAO,KAAK,QAAQ,OAAO,GAAG;CACpC,MAAM,QAAQ;CACd,MAAM,MAAiB,EAAE;CACzB,IAAI,OAAO;AAEX,QAAO,MAAM;EACX,MAAM,MAAM,MAAM,MAChB,GAAG,KAAK,gCAAgC,MAAM,QAAQ,QACtD;GACE,SAAS,EAAE,eAAe,SAAS,QAAQ;GAC3C,QAAQ,WAAW;GACpB,CACF;AACD,MAAI,CAAC,IAAI,GACP,OAAM,IAAI,iBACR,IAAI,QACJ,MAAM,wBAAwB,KAAK,WAAW,CAC/C;EAEH,MAAM,OAAO,MAAM,IAAI,MAAM;EAC7B,MAAM,QAAQ,MAAM,QAAQ,KAAK,GAAG,OAAQ,KAAK,QAAQ,EAAE;AAC3D,MAAI,KAAK,GAAG,MAAM;EAElB,MAAM,aAAa,OAAO,KAAK,MAAM,cAAc,KAAK,MAAM,YAAY;AAC1E,MAAI,OAAO,SAAS,WAAW,IAAI,QAAQ,WAAY;AACvD,MAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,MAAM,SAAS,MAAO;AACnD;;AAGF,QAAO;;AAGT,IAAM,mBAAN,cAA+B,MAAM;CACnC;CAEA,YAAmB,QAAgB,SAAiB;AAClD,QAAM,QAAQ;AACd,OAAK,SAAS"}
1
+ {"version":3,"file":"langfuse-datasets.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-datasets.ts"],"sourcesContent":["import {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasets,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nexport default async function handler(\n _req: StudioRequest,\n res: StudioResponse,\n) {\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n res\n .status(200)\n .json({ data: getDemoDatasets(), connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ data: [], connection: creds.connection });\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const datasets = await fetchDatasets(creds.host, creds.auth, controller);\n res.status(200).json({ data: datasets, connection: creds.connection });\n } catch (err) {\n const detail =\n err instanceof Error && err.message.startsWith(\"Kaizen could not load\")\n ? err.message\n : formatLangfuseRequestError(err);\n const state =\n err instanceof LangfuseApiError &&\n (err.status === 401 || err.status === 403)\n ? \"auth_failed\"\n : err instanceof LangfuseApiError\n ? \"query_failed\"\n : \"network_error\";\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(state, { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nasync function fetchDatasets(\n host: string,\n auth: string,\n controller: AbortController,\n): Promise<unknown[]> {\n const base = host.replace(/\\/$/, \"\");\n const limit = 100;\n const out: unknown[] = [];\n let page = 1;\n\n while (true) {\n const res = await fetch(\n `${base}/api/public/v2/datasets?limit=${limit}&page=${page}`,\n {\n headers: { Authorization: `Basic ${auth}` },\n signal: controller.signal,\n },\n );\n if (!res.ok) {\n throw new LangfuseApiError(\n res.status,\n await formatLangfuseHttpError(res, \"datasets\"),\n );\n }\n const body = await res.json();\n const items = Array.isArray(body) ? body : (body.data ?? []);\n out.push(...items);\n\n const totalPages = Number(body.meta?.totalPages ?? body.meta?.total_pages);\n if (Number.isFinite(totalPages) && page >= totalPages) break;\n if (!Array.isArray(items) || items.length < limit) break;\n page++;\n }\n\n return out;\n}\n\nclass LangfuseApiError extends Error {\n public readonly status: number;\n\n public constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n"],"mappings":";;;;AAkBA,eAA8B,QAC5B,MACA,KACA;CACA,MAAM,QAAQ,qBAAqB;CACnC,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,MAAM;EAC9B,IAAI,WAAW,GAAG;GAChB,IACG,OAAO,GAAG,EACV,KAAK;IAAE,MAAM,gBAAgB;IAAG,YAAY;GAAgB,CAAC;GAChE;EACF;EACA,IAAI,OAAO,GAAG,EAAE,KAAK;GAAE,MAAM,CAAC;GAAG,YAAY,MAAM;EAAW,CAAC;EAC/D;CACF;CAEA,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAM;CAC3D,IAAI;EACF,MAAM,WAAW,MAAM,cAAc,MAAM,MAAM,MAAM,MAAM,UAAU;EACvE,IAAI,OAAO,GAAG,EAAE,KAAK;GAAE,MAAM;GAAU,YAAY,MAAM;EAAW,CAAC;CACvE,SAAS,KAAK;EACZ,MAAM,SACJ,eAAe,SAAS,IAAI,QAAQ,WAAW,uBAAuB,IAClE,IAAI,UACJ,2BAA2B,GAAG;EACpC,MAAM,QACJ,eAAe,qBACd,IAAI,WAAW,OAAO,IAAI,WAAW,OAClC,gBACA,eAAe,mBACb,iBACA;EACR,IAAI,OAAO,GAAG,EAAE,KAAK;GACnB,OAAO;GACP,YAAY,eAAe,OAAO,EAAE,OAAO,CAAC;EAC9C,CAAC;CACH,UAAU;EACR,aAAa,OAAO;CACtB;AACF;AAEA,eAAe,cACb,MACA,MACA,YACoB;CACpB,MAAM,OAAO,KAAK,QAAQ,OAAO,EAAE;CACnC,MAAM,QAAQ;CACd,MAAM,MAAiB,CAAC;CACxB,IAAI,OAAO;CAEX,OAAO,MAAM;EACX,MAAM,MAAM,MAAM,MAChB,GAAG,KAAK,gCAAgC,MAAM,QAAQ,QACtD;GACE,SAAS,EAAE,eAAe,SAAS,OAAO;GAC1C,QAAQ,WAAW;EACrB,CACF;EACA,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,iBACR,IAAI,QACJ,MAAM,wBAAwB,KAAK,UAAU,CAC/C;EAEF,MAAM,OAAO,MAAM,IAAI,KAAK;EAC5B,MAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAQ,KAAK,QAAQ,CAAC;EAC1D,IAAI,KAAK,GAAG,KAAK;EAEjB,MAAM,aAAa,OAAO,KAAK,MAAM,cAAc,KAAK,MAAM,WAAW;EACzE,IAAI,OAAO,SAAS,UAAU,KAAK,QAAQ,YAAY;EACvD,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,OAAO;EACnD;CACF;CAEA,OAAO;AACT;AAEA,IAAM,mBAAN,cAA+B,MAAM;CACnC;CAEA,YAAmB,QAAgB,SAAiB;EAClD,MAAM,OAAO;EACb,KAAK,SAAS;CAChB;AACF"}
@@ -1,7 +1,7 @@
1
+ import { getSourceTraceId, isRecord } from "../../src/lib/langfuse-helpers.js";
1
2
  import { langfuseStatus, resolveLangfuseCreds } from "../../src/lib/langfuse-creds.js";
2
3
  import { DEMO_CONNECTION, getDemoDatasetItems, getDemoDatasets, isDemoMode } from "../../src/lib/langfuse-demo.js";
3
4
  import { formatLangfuseHttpError, formatLangfuseRequestError } from "../../src/lib/langfuse-errors.js";
4
- import { getSourceTraceId, isRecord } from "../../src/lib/langfuse-helpers.js";
5
5
  import { createTtlCache } from "../../src/lib/langfuse-cache.js";
6
6
  //#region dashboard/pages/api/langfuse-trace-memberships.ts
7
7
  const membershipsCache = createTtlCache({
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-trace-memberships.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-trace-memberships.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasetItems,\n getDemoDatasets,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { getSourceTraceId, isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface Membership {\n datasetName: string;\n datasetItemId: string;\n status: string;\n}\n\nexport const membershipsCache = createTtlCache({ ttlMs: 30_000, maxSize: 500 });\nconst cache = membershipsCache;\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { traceId, noCache } = req.query;\n if (typeof traceId !== \"string\" || traceId === \"\") {\n res.status(400).json({ error: \"traceId query param required\" });\n return;\n }\n const skipCache = noCache === \"1\";\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const datasets = getDemoDatasets();\n const memberships: Membership[] = [];\n for (const ds of datasets) {\n const items = getDemoDatasetItems(ds.name);\n for (const item of items) {\n if (\n isRecord(item) &&\n typeof item.id === \"string\" &&\n getSourceTraceId(item) === traceId &&\n (typeof item.status !== \"string\" ||\n item.status.toUpperCase() !== \"ARCHIVED\")\n ) {\n memberships.push({\n datasetName: ds.name,\n datasetItemId: item.id,\n status: typeof item.status === \"string\" ? item.status : \"ACTIVE\",\n });\n }\n }\n }\n res.status(200).json({ memberships, connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ memberships: [], connection: creds.connection });\n return;\n }\n\n const cacheKey = `tm:${traceId}`;\n if (!skipCache) {\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n try {\n // Fetch all datasets (paginated)\n const base = creds.host.replace(/\\/$/, \"\");\n const datasetList: Array<{ name: string }> = [];\n let dsPage = 1;\n const dsLimit = 100;\n while (true) {\n const datasetsRes = await fetch(\n `${base}/api/public/v2/datasets?limit=${dsLimit}&page=${dsPage}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!datasetsRes.ok) {\n if (dsPage === 1) {\n const detail = await formatLangfuseHttpError(datasetsRes, \"datasets\");\n res.status(datasetsRes.status).json({\n error: detail,\n connection: langfuseStatus(\n datasetsRes.status === 401 || datasetsRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n break;\n }\n const datasetsBody = await datasetsRes.json();\n const pageData: unknown[] = Array.isArray(datasetsBody)\n ? datasetsBody\n : (datasetsBody.data ?? []);\n for (const d of pageData) {\n if (isRecord(d) && typeof d.name === \"string\") {\n datasetList.push({ name: d.name });\n }\n }\n if (pageData.length < dsLimit) break;\n dsPage++;\n }\n\n // Fan out across all datasets server-side with concurrency limit\n const CONCURRENCY = 5;\n const taskResults: Array<PromiseSettledResult<Membership[] | null>> = [];\n for (let i = 0; i < datasetList.length; i += CONCURRENCY) {\n const batch = datasetList.slice(i, i + CONCURRENCY);\n const batchResults = await Promise.allSettled(\n batch.map(async (ds) => {\n const itemsRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(ds.name)}&limit=100&page=1`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!itemsRes.ok) return null;\n const itemsBody = await itemsRes.json();\n const items: unknown[] = itemsBody.data ?? [];\n\n // Paginate through all pages using length heuristic\n const limit = 100;\n let allItems = items;\n let hasMore = items.length >= limit;\n let page = 1;\n while (hasMore && page < 20) {\n page++;\n const nextRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(ds.name)}&limit=${limit}&page=${page}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!nextRes.ok) return null;\n const nextBody = await nextRes.json();\n const nextItems: unknown[] = nextBody.data ?? [];\n allItems = allItems.concat(nextItems);\n hasMore = nextItems.length >= limit;\n }\n\n const matches: Membership[] = [];\n for (const rawItem of allItems) {\n if (!isRecord(rawItem) || typeof rawItem.id !== \"string\") continue;\n const status =\n typeof rawItem.status === \"string\" ? rawItem.status : \"ACTIVE\";\n if (status.toUpperCase() === \"ARCHIVED\") continue;\n if (getSourceTraceId(rawItem) === traceId) {\n matches.push({\n datasetName: ds.name,\n datasetItemId: rawItem.id,\n status,\n });\n }\n }\n return matches;\n }),\n );\n taskResults.push(...batchResults);\n }\n\n const memberships: Membership[] = [];\n let allSucceeded = true;\n for (const result of taskResults) {\n if (result.status === \"fulfilled\" && result.value) {\n memberships.push(...result.value);\n } else {\n allSucceeded = false;\n }\n }\n\n const body = { memberships, connection: creds.connection };\n // Only cache when every dataset was fetched successfully; partial results\n // from timeouts or errors should not be cached.\n if (allSucceeded) {\n cache.set(cacheKey, body);\n }\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"langfuse-trace-memberships.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-trace-memberships.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport {\n langfuseStatus,\n resolveLangfuseCreds,\n} from \"../../src/lib/langfuse-creds\";\nimport {\n DEMO_CONNECTION,\n getDemoDatasetItems,\n getDemoDatasets,\n isDemoMode,\n} from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport { getSourceTraceId, isRecord } from \"../../src/lib/langfuse-helpers\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\ninterface Membership {\n datasetName: string;\n datasetItemId: string;\n status: string;\n}\n\nexport const membershipsCache = createTtlCache({ ttlMs: 30_000, maxSize: 500 });\nconst cache = membershipsCache;\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { traceId, noCache } = req.query;\n if (typeof traceId !== \"string\" || traceId === \"\") {\n res.status(400).json({ error: \"traceId query param required\" });\n return;\n }\n const skipCache = noCache === \"1\";\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const datasets = getDemoDatasets();\n const memberships: Membership[] = [];\n for (const ds of datasets) {\n const items = getDemoDatasetItems(ds.name);\n for (const item of items) {\n if (\n isRecord(item) &&\n typeof item.id === \"string\" &&\n getSourceTraceId(item) === traceId &&\n (typeof item.status !== \"string\" ||\n item.status.toUpperCase() !== \"ARCHIVED\")\n ) {\n memberships.push({\n datasetName: ds.name,\n datasetItemId: item.id,\n status: typeof item.status === \"string\" ? item.status : \"ACTIVE\",\n });\n }\n }\n }\n res.status(200).json({ memberships, connection: DEMO_CONNECTION });\n return;\n }\n res.status(200).json({ memberships: [], connection: creds.connection });\n return;\n }\n\n const cacheKey = `tm:${traceId}`;\n if (!skipCache) {\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 30_000);\n try {\n // Fetch all datasets (paginated)\n const base = creds.host.replace(/\\/$/, \"\");\n const datasetList: Array<{ name: string }> = [];\n let dsPage = 1;\n const dsLimit = 100;\n while (true) {\n const datasetsRes = await fetch(\n `${base}/api/public/v2/datasets?limit=${dsLimit}&page=${dsPage}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!datasetsRes.ok) {\n if (dsPage === 1) {\n const detail = await formatLangfuseHttpError(datasetsRes, \"datasets\");\n res.status(datasetsRes.status).json({\n error: detail,\n connection: langfuseStatus(\n datasetsRes.status === 401 || datasetsRes.status === 403\n ? \"auth_failed\"\n : \"query_failed\",\n { detail },\n ),\n });\n return;\n }\n break;\n }\n const datasetsBody = await datasetsRes.json();\n const pageData: unknown[] = Array.isArray(datasetsBody)\n ? datasetsBody\n : (datasetsBody.data ?? []);\n for (const d of pageData) {\n if (isRecord(d) && typeof d.name === \"string\") {\n datasetList.push({ name: d.name });\n }\n }\n if (pageData.length < dsLimit) break;\n dsPage++;\n }\n\n // Fan out across all datasets server-side with concurrency limit\n const CONCURRENCY = 5;\n const taskResults: Array<PromiseSettledResult<Membership[] | null>> = [];\n for (let i = 0; i < datasetList.length; i += CONCURRENCY) {\n const batch = datasetList.slice(i, i + CONCURRENCY);\n const batchResults = await Promise.allSettled(\n batch.map(async (ds) => {\n const itemsRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(ds.name)}&limit=100&page=1`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!itemsRes.ok) return null;\n const itemsBody = await itemsRes.json();\n const items: unknown[] = itemsBody.data ?? [];\n\n // Paginate through all pages using length heuristic\n const limit = 100;\n let allItems = items;\n let hasMore = items.length >= limit;\n let page = 1;\n while (hasMore && page < 20) {\n page++;\n const nextRes = await fetch(\n `${base}/api/public/dataset-items?datasetName=${encodeURIComponent(ds.name)}&limit=${limit}&page=${page}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!nextRes.ok) return null;\n const nextBody = await nextRes.json();\n const nextItems: unknown[] = nextBody.data ?? [];\n allItems = allItems.concat(nextItems);\n hasMore = nextItems.length >= limit;\n }\n\n const matches: Membership[] = [];\n for (const rawItem of allItems) {\n if (!isRecord(rawItem) || typeof rawItem.id !== \"string\") continue;\n const status =\n typeof rawItem.status === \"string\" ? rawItem.status : \"ACTIVE\";\n if (status.toUpperCase() === \"ARCHIVED\") continue;\n if (getSourceTraceId(rawItem) === traceId) {\n matches.push({\n datasetName: ds.name,\n datasetItemId: rawItem.id,\n status,\n });\n }\n }\n return matches;\n }),\n );\n taskResults.push(...batchResults);\n }\n\n const memberships: Membership[] = [];\n let allSucceeded = true;\n for (const result of taskResults) {\n if (result.status === \"fulfilled\" && result.value) {\n memberships.push(...result.value);\n } else {\n allSucceeded = false;\n }\n }\n\n const body = { memberships, connection: creds.connection };\n // Only cache when every dataset was fetched successfully; partial results\n // from timeouts or errors should not be cached.\n if (allSucceeded) {\n cache.set(cacheKey, body);\n }\n res.status(200).json(body);\n } catch (err) {\n const detail = formatLangfuseRequestError(err);\n res.status(502).json({\n error: detail,\n connection: langfuseStatus(\"network_error\", { detail }),\n });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse-trace.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-trace.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport { getDemoTrace, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nconst cache = createTtlCache({ ttlMs: 30_000 });\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { traceId } = req.query;\n if (typeof traceId !== \"string\" || traceId === \"\") {\n res.status(400).json({ error: \"traceId query param required\" });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const trace = getDemoTrace(traceId);\n if (trace) {\n res.status(200).json(trace);\n return;\n }\n res\n .status(404)\n .json({ error: `Trace ${traceId} not found in demo data.` });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const cacheKey = `trace:${traceId}`;\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/traces/${encodeURIComponent(traceId)}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n res\n .status(lfRes.status)\n .json({ error: await formatLangfuseHttpError(lfRes, \"the trace\") });\n return;\n }\n const data = await lfRes.json();\n cache.set(cacheKey, data);\n res.status(200).json(data);\n } catch (err) {\n res.status(502).json({ error: formatLangfuseRequestError(err) });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":";;;;;AAYA,MAAM,QAAQ,eAAe,EAAE,OAAO,KAAQ,CAAC;AAE/C,eAA8B,QAAQ,KAAoB,KAAqB;CAC7E,MAAM,EAAE,YAAY,IAAI;AACxB,KAAI,OAAO,YAAY,YAAY,YAAY,IAAI;AACjD,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,gCAAgC,CAAC;AAC/D;;CAGF,MAAM,QAAQ,sBAAsB;AACpC,KAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,MAAM;AAC9B,MAAI,YAAY,EAAE;GAChB,MAAM,QAAQ,aAAa,QAAQ;AACnC,OAAI,OAAO;AACT,QAAI,OAAO,IAAI,CAAC,KAAK,MAAM;AAC3B;;AAEF,OACG,OAAO,IAAI,CACX,KAAK,EAAE,OAAO,SAAS,QAAQ,2BAA2B,CAAC;AAC9D;;AAEF,MAAI,OAAO,IAAI,CAAC,KAAK;GACnB,OAAO,MAAM,WAAW;GACxB,YAAY,MAAM;GACnB,CAAC;AACF;;CAGF,MAAM,WAAW,SAAS;CAC1B,MAAM,SAAS,MAAM,IAAI,SAAS;AAClC,KAAI,WAAW,KAAA,GAAW;AACxB,MAAI,OAAO,IAAI,CAAC,KAAK,OAAO;AAC5B;;CAGF,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,UAAU,iBAAiB,WAAW,OAAO,EAAE,IAAO;AAC5D,KAAI;EACF,MAAM,QAAQ,MAAM,MAClB,GAAG,MAAM,KAAK,QAAQ,OAAO,GAAG,CAAC,qBAAqB,mBAAmB,QAAQ,IACjF;GACE,SAAS,EAAE,eAAe,SAAS,MAAM,QAAQ;GACjD,QAAQ,WAAW;GACpB,CACF;AACD,MAAI,CAAC,MAAM,IAAI;AACb,OACG,OAAO,MAAM,OAAO,CACpB,KAAK,EAAE,OAAO,MAAM,wBAAwB,OAAO,YAAY,EAAE,CAAC;AACrE;;EAEF,MAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,QAAM,IAAI,UAAU,KAAK;AACzB,MAAI,OAAO,IAAI,CAAC,KAAK,KAAK;UACnB,KAAK;AACZ,MAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2BAA2B,IAAI,EAAE,CAAC;WACxD;AACR,eAAa,QAAQ"}
1
+ {"version":3,"file":"langfuse-trace.js","names":[],"sources":["../../../../dashboard/pages/api/langfuse-trace.ts"],"sourcesContent":["import { createTtlCache } from \"../../src/lib/langfuse-cache\";\nimport { resolveLangfuseCreds } from \"../../src/lib/langfuse-creds\";\nimport { getDemoTrace, isDemoMode } from \"../../src/lib/langfuse-demo\";\nimport {\n formatLangfuseHttpError,\n formatLangfuseRequestError,\n} from \"../../src/lib/langfuse-errors\";\nimport type {\n StudioRequest,\n StudioResponse,\n} from \"../../src/studio/server/compat\";\n\nconst cache = createTtlCache({ ttlMs: 30_000 });\n\nexport default async function handler(req: StudioRequest, res: StudioResponse) {\n const { traceId } = req.query;\n if (typeof traceId !== \"string\" || traceId === \"\") {\n res.status(400).json({ error: \"traceId query param required\" });\n return;\n }\n\n const creds = resolveLangfuseCreds();\n if (!creds.host || !creds.auth) {\n if (isDemoMode()) {\n const trace = getDemoTrace(traceId);\n if (trace) {\n res.status(200).json(trace);\n return;\n }\n res\n .status(404)\n .json({ error: `Trace ${traceId} not found in demo data.` });\n return;\n }\n res.status(400).json({\n error: creds.connection.message,\n connection: creds.connection,\n });\n return;\n }\n\n const cacheKey = `trace:${traceId}`;\n const cached = cache.get(cacheKey);\n if (cached !== undefined) {\n res.status(200).json(cached);\n return;\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 10_000);\n try {\n const lfRes = await fetch(\n `${creds.host.replace(/\\/$/, \"\")}/api/public/traces/${encodeURIComponent(traceId)}`,\n {\n headers: { Authorization: `Basic ${creds.auth}` },\n signal: controller.signal,\n },\n );\n if (!lfRes.ok) {\n res\n .status(lfRes.status)\n .json({ error: await formatLangfuseHttpError(lfRes, \"the trace\") });\n return;\n }\n const data = await lfRes.json();\n cache.set(cacheKey, data);\n res.status(200).json(data);\n } catch (err) {\n res.status(502).json({ error: formatLangfuseRequestError(err) });\n } finally {\n clearTimeout(timeout);\n }\n}\n"],"mappings":";;;;;AAYA,MAAM,QAAQ,eAAe,EAAE,OAAO,IAAO,CAAC;AAE9C,eAA8B,QAAQ,KAAoB,KAAqB;CAC7E,MAAM,EAAE,YAAY,IAAI;CACxB,IAAI,OAAO,YAAY,YAAY,YAAY,IAAI;EACjD,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,+BAA+B,CAAC;EAC9D;CACF;CAEA,MAAM,QAAQ,qBAAqB;CACnC,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,MAAM;EAC9B,IAAI,WAAW,GAAG;GAChB,MAAM,QAAQ,aAAa,OAAO;GAClC,IAAI,OAAO;IACT,IAAI,OAAO,GAAG,EAAE,KAAK,KAAK;IAC1B;GACF;GACA,IACG,OAAO,GAAG,EACV,KAAK,EAAE,OAAO,SAAS,QAAQ,0BAA0B,CAAC;GAC7D;EACF;EACA,IAAI,OAAO,GAAG,EAAE,KAAK;GACnB,OAAO,MAAM,WAAW;GACxB,YAAY,MAAM;EACpB,CAAC;EACD;CACF;CAEA,MAAM,WAAW,SAAS;CAC1B,MAAM,SAAS,MAAM,IAAI,QAAQ;CACjC,IAAI,WAAW,KAAA,GAAW;EACxB,IAAI,OAAO,GAAG,EAAE,KAAK,MAAM;EAC3B;CACF;CAEA,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAM;CAC3D,IAAI;EACF,MAAM,QAAQ,MAAM,MAClB,GAAG,MAAM,KAAK,QAAQ,OAAO,EAAE,EAAE,qBAAqB,mBAAmB,OAAO,KAChF;GACE,SAAS,EAAE,eAAe,SAAS,MAAM,OAAO;GAChD,QAAQ,WAAW;EACrB,CACF;EACA,IAAI,CAAC,MAAM,IAAI;GACb,IACG,OAAO,MAAM,MAAM,EACnB,KAAK,EAAE,OAAO,MAAM,wBAAwB,OAAO,WAAW,EAAE,CAAC;GACpE;EACF;EACA,MAAM,OAAO,MAAM,MAAM,KAAK;EAC9B,MAAM,IAAI,UAAU,IAAI;EACxB,IAAI,OAAO,GAAG,EAAE,KAAK,IAAI;CAC3B,SAAS,KAAK;EACZ,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,GAAG,EAAE,CAAC;CACjE,UAAU;EACR,aAAa,OAAO;CACtB;AACF"}