@atlanai/sdk 0.2.2 → 0.2.3

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 (145) hide show
  1. package/README.md +6 -2
  2. package/dist/client.d.ts +4 -1
  3. package/dist/client.js +13 -1
  4. package/dist/errors.d.ts +1 -0
  5. package/dist/errors.js +12 -1
  6. package/dist/evals.d.ts +5 -1
  7. package/dist/evals.js +285 -70
  8. package/dist/index.d.ts +1 -0
  9. package/dist/manifest.json +84 -11
  10. package/dist/raw/agent/apis/AgentsApi.d.ts +29 -0
  11. package/dist/raw/agent/apis/AgentsApi.js +62 -1
  12. package/dist/raw/agent/apis/SessionsApi.d.ts +2 -2
  13. package/dist/raw/agent/apis/SessionsApi.js +2 -2
  14. package/dist/raw/agent/models/AgentAgentArtifactView.d.ts +48 -0
  15. package/dist/raw/agent/models/AgentAgentArtifactView.js +13 -0
  16. package/dist/raw/agent/models/AgentAgentEnvironmentArtifactView.d.ts +12 -0
  17. package/dist/raw/agent/models/AgentAgentEnvironmentArtifactView.js +2 -0
  18. package/dist/raw/agent/models/AgentAgentEnvironmentMeta.d.ts +12 -0
  19. package/dist/raw/agent/models/AgentAgentEnvironmentMeta.js +2 -0
  20. package/dist/raw/agent/models/AgentAgentMeta.d.ts +48 -0
  21. package/dist/raw/agent/models/AgentAgentMeta.js +13 -0
  22. package/dist/raw/agent/models/AgentAgentProviderArtifactView.d.ts +55 -2
  23. package/dist/raw/agent/models/AgentAgentProviderArtifactView.js +8 -0
  24. package/dist/raw/agent/models/AgentAgentProviderCreate.d.ts +24 -3
  25. package/dist/raw/agent/models/AgentAgentProviderCreate.js +4 -0
  26. package/dist/raw/agent/models/AgentAgentProviderMeta.d.ts +56 -13
  27. package/dist/raw/agent/models/AgentAgentProviderMeta.js +8 -0
  28. package/dist/raw/agent/models/AgentAgentProviderPatch.d.ts +13 -0
  29. package/dist/raw/agent/models/AgentAgentProviderPatch.js +4 -0
  30. package/dist/raw/agent/models/AgentAgentProviderType.d.ts +19 -9
  31. package/dist/raw/agent/models/AgentAgentProviderType.js +18 -8
  32. package/dist/raw/agent/models/AgentListAgentFrameworks401Response.d.ts +1 -0
  33. package/dist/raw/agent/models/AgentListAgentFrameworks401Response.js +1 -0
  34. package/dist/raw/agent/models/AgentPageSessionEventView.d.ts +3 -3
  35. package/dist/raw/agent/models/AgentPageSessionEventView.js +3 -3
  36. package/dist/raw/agent/models/AgentRemoteSessionEvent.d.ts +38 -0
  37. package/dist/raw/agent/models/AgentRemoteSessionEvent.js +58 -0
  38. package/dist/raw/agent/models/AgentSessionArtifactView.d.ts +25 -0
  39. package/dist/raw/agent/models/AgentSessionArtifactView.js +2 -0
  40. package/dist/raw/agent/models/AgentSessionEventView.d.ts +30 -0
  41. package/dist/raw/agent/models/AgentSessionEventView.js +57 -0
  42. package/dist/raw/agent/models/AgentSessionMeta.d.ts +25 -0
  43. package/dist/raw/agent/models/AgentSessionMeta.js +2 -0
  44. package/dist/raw/agent/models/AgentSessionStreamFrame.d.ts +8 -6
  45. package/dist/raw/agent/models/AgentSessionStreamFrame.js +7 -0
  46. package/dist/raw/agent/models/AgentSyncState.d.ts +27 -0
  47. package/dist/raw/agent/models/AgentSyncState.js +55 -0
  48. package/dist/raw/agent/models/TriggerBody.d.ts +52 -0
  49. package/dist/raw/agent/models/TriggerBody.js +58 -0
  50. package/dist/raw/agent/models/index.d.ts +4 -1
  51. package/dist/raw/agent/models/index.js +4 -1
  52. package/dist/raw/eval/models/EvalListDatasets401Response.d.ts +1 -0
  53. package/dist/raw/eval/models/EvalListDatasets401Response.js +1 -0
  54. package/dist/raw/file/models/FileList401Response.d.ts +1 -0
  55. package/dist/raw/file/models/FileList401Response.js +1 -0
  56. package/dist/raw/mcp/apis/ServersApi.d.ts +2 -2
  57. package/dist/raw/mcp/apis/ServersApi.js +2 -2
  58. package/dist/raw/mcp/models/McpConnectionState.d.ts +2 -1
  59. package/dist/raw/mcp/models/McpConnectionState.js +2 -1
  60. package/dist/raw/mcp/models/McpListServers401Response.d.ts +1 -0
  61. package/dist/raw/mcp/models/McpListServers401Response.js +1 -0
  62. package/dist/raw/mcp/models/McpServerTestResponse.d.ts +13 -2
  63. package/dist/raw/model/models/ModelListProviders401Response.d.ts +1 -0
  64. package/dist/raw/model/models/ModelListProviders401Response.js +1 -0
  65. package/dist/raw/registry/apis/WorkspacesApi.d.ts +28 -0
  66. package/dist/raw/registry/apis/WorkspacesApi.js +61 -1
  67. package/dist/raw/registry/models/CreateWorkspaceBody.d.ts +9 -0
  68. package/dist/raw/registry/models/CreateWorkspaceBody.js +2 -0
  69. package/dist/raw/registry/models/RegistryAggregate401Response.d.ts +1 -0
  70. package/dist/raw/registry/models/RegistryAggregate401Response.js +1 -0
  71. package/dist/raw/registry/models/RegistryWorkspaceView.d.ts +9 -0
  72. package/dist/raw/registry/models/RegistryWorkspaceView.js +4 -0
  73. package/dist/raw/registry/models/SearchRequest.d.ts +1 -1
  74. package/dist/raw/registry/models/SetVisibilityBody.d.ts +33 -0
  75. package/dist/raw/registry/models/SetVisibilityBody.js +50 -0
  76. package/dist/raw/registry/models/index.d.ts +1 -0
  77. package/dist/raw/registry/models/index.js +1 -0
  78. package/dist/raw/secret/apis/OauthApi.d.ts +143 -0
  79. package/dist/raw/secret/apis/OauthApi.js +335 -0
  80. package/dist/raw/secret/apis/index.d.ts +1 -0
  81. package/dist/raw/secret/apis/index.js +19 -0
  82. package/dist/raw/secret/index.d.ts +1 -0
  83. package/dist/raw/secret/index.js +1 -0
  84. package/dist/raw/secret/models/GatewayOauthCompleteRequest.d.ts +32 -0
  85. package/dist/raw/secret/models/GatewayOauthCompleteRequest.js +50 -0
  86. package/dist/raw/secret/models/GatewayOauthCompletion.d.ts +46 -0
  87. package/dist/raw/secret/models/GatewayOauthCompletion.js +55 -0
  88. package/dist/raw/secret/models/GatewayOauthConnection.d.ts +83 -0
  89. package/dist/raw/secret/models/GatewayOauthConnection.js +83 -0
  90. package/dist/raw/secret/models/GatewayOauthOwner.d.ts +31 -0
  91. package/dist/raw/secret/models/GatewayOauthOwner.js +59 -0
  92. package/dist/raw/secret/models/GatewayOauthProviderView.d.ts +69 -0
  93. package/dist/raw/secret/models/GatewayOauthProviderView.js +69 -0
  94. package/dist/raw/secret/models/GatewayOauthStartRequest.d.ts +52 -0
  95. package/dist/raw/secret/models/GatewayOauthStartRequest.js +59 -0
  96. package/dist/raw/secret/models/GatewayOauthStarted.d.ts +39 -0
  97. package/dist/raw/secret/models/GatewayOauthStarted.js +54 -0
  98. package/dist/raw/secret/models/SecretCompleteOauth400Response.d.ts +70 -0
  99. package/dist/raw/secret/models/SecretCompleteOauth400Response.js +80 -0
  100. package/dist/raw/secret/models/SecretCompleteOauth413Response.d.ts +64 -0
  101. package/dist/raw/secret/models/SecretCompleteOauth413Response.js +74 -0
  102. package/dist/raw/secret/models/SecretCompleteOauth415Response.d.ts +64 -0
  103. package/dist/raw/secret/models/SecretCompleteOauth415Response.js +74 -0
  104. package/dist/raw/secret/models/SecretCompleteOauth422Response.d.ts +63 -0
  105. package/dist/raw/secret/models/SecretCompleteOauth422Response.js +75 -0
  106. package/dist/raw/secret/models/SecretDisconnectOauth400Response.d.ts +67 -0
  107. package/dist/raw/secret/models/SecretDisconnectOauth400Response.js +77 -0
  108. package/dist/raw/secret/models/SecretOauthCallback400Response.d.ts +68 -0
  109. package/dist/raw/secret/models/SecretOauthCallback400Response.js +78 -0
  110. package/dist/raw/secret/models/SecretOauthCallback401Response.d.ts +80 -0
  111. package/dist/raw/secret/models/SecretOauthCallback401Response.js +90 -0
  112. package/dist/raw/secret/models/SecretOauthCallback403Response.d.ts +66 -0
  113. package/dist/raw/secret/models/SecretOauthCallback403Response.js +76 -0
  114. package/dist/raw/secret/models/SecretOauthCallback404Response.d.ts +65 -0
  115. package/dist/raw/secret/models/SecretOauthCallback404Response.js +75 -0
  116. package/dist/raw/secret/models/SecretOauthCallback500Response.d.ts +64 -0
  117. package/dist/raw/secret/models/SecretOauthCallback500Response.js +74 -0
  118. package/dist/raw/secret/models/SecretOauthCallback503Response.d.ts +66 -0
  119. package/dist/raw/secret/models/SecretOauthCallback503Response.js +76 -0
  120. package/dist/raw/secret/models/SecretOauthCallback504Response.d.ts +63 -0
  121. package/dist/raw/secret/models/SecretOauthCallback504Response.js +75 -0
  122. package/dist/raw/secret/models/index.d.ts +19 -0
  123. package/dist/raw/secret/models/index.js +19 -0
  124. package/dist/raw/skill/models/SkillInsightSchemas401Response.d.ts +1 -0
  125. package/dist/raw/skill/models/SkillInsightSchemas401Response.js +1 -0
  126. package/dist/resource-types.d.ts +670 -0
  127. package/dist/resource-types.js +2 -0
  128. package/dist/resources.json +86 -0
  129. package/dist/tracing/client.d.ts +8 -1
  130. package/dist/tracing/client.js +43 -16
  131. package/dist/tracing/config.d.ts +3 -1
  132. package/dist/tracing/config.js +7 -2
  133. package/dist/tracing/exporter.d.ts +19 -1
  134. package/dist/tracing/exporter.js +56 -14
  135. package/dist/tracing/propagation.d.ts +2 -0
  136. package/dist/tracing/propagation.js +16 -0
  137. package/dist/tracing/semconv.d.ts +4 -0
  138. package/dist/tracing/semconv.js +14 -2
  139. package/dist/tracing/spans.d.ts +2 -0
  140. package/dist/tracing/spans.js +12 -1
  141. package/dist/tracing/version.d.ts +1 -1
  142. package/dist/tracing/version.js +1 -1
  143. package/package.json +1 -1
  144. package/dist/raw/agent/models/Box.d.ts +0 -342
  145. package/dist/raw/agent/models/Box.js +0 -167
package/dist/evals.js CHANGED
@@ -62,6 +62,9 @@ function manifestItem(item) {
62
62
  if (item.version.toLowerCase() === "latest") {
63
63
  throw new Error("context item version must be pinned; 'latest' is not a version");
64
64
  }
65
+ if (item.artifactKind !== undefined && !CONTEXT_KIND.test(item.artifactKind)) {
66
+ throw new Error("context item artifactKind must be a lowercase identifier");
67
+ }
65
68
  if (item.artifactId !== undefined) {
66
69
  validText(item.artifactId, "context item artifactId", 200);
67
70
  }
@@ -69,11 +72,18 @@ function manifestItem(item) {
69
72
  (!Number.isInteger(item.versionOrdinal) || item.versionOrdinal < 1)) {
70
73
  throw new Error("context item versionOrdinal must be an integer of at least 1");
71
74
  }
75
+ if ((item.artifactId === undefined) !== (item.versionOrdinal === undefined)) {
76
+ throw new Error("context item Registry pins require artifactId and versionOrdinal");
77
+ }
78
+ if (item.artifactKind !== undefined && item.artifactId === undefined) {
79
+ throw new Error("context item artifactKind requires a Registry artifact pin");
80
+ }
72
81
  return {
73
82
  kind: item.kind,
74
83
  name: item.name,
75
84
  digest: item.digest,
76
85
  version: item.version,
86
+ ...(item.artifactKind === undefined ? {} : { artifact_kind: item.artifactKind }),
77
87
  ...(item.artifactId === undefined ? {} : { artifact_id: item.artifactId }),
78
88
  ...(item.versionOrdinal === undefined ? {} : { version_ordinal: item.versionOrdinal }),
79
89
  };
@@ -237,7 +247,7 @@ async function startExperiment(client, dataset, body, options = {}) {
237
247
  }
238
248
  // ---------------------------------------------------------------------------
239
249
  // Declarative evaluator
240
- const DEFAULT_GATEWAY_ORIGIN = "https://agentgateway.atlan.engineering";
250
+ const DEFAULT_GATEWAY_ORIGIN = "https://api.atlan.com";
241
251
  function tracingModule() {
242
252
  // The root client is CommonJS while tracing has its own NodeNext build. A
243
253
  // runtime require keeps the two compiler targets independent and resolves to
@@ -311,6 +321,20 @@ async function collectData(data) {
311
321
  }
312
322
  return cases;
313
323
  }
324
+ async function casesDigest(cases) {
325
+ const normalized = cases.map((item, index) => ({
326
+ id: item.id ?? `case-${index + 1}`,
327
+ input: item.input,
328
+ expected: item.expected ?? null,
329
+ metadata: item.metadata ?? {},
330
+ tags: item.tags ?? [],
331
+ }));
332
+ if (new Set(normalized.map((item) => item.id)).size !== cases.length) {
333
+ throw new Error("Eval case IDs must be unique");
334
+ }
335
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(normalized)));
336
+ return "sha256:" + [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
337
+ }
314
338
  async function datasetCases(resources, datasetId) {
315
339
  const records = [];
316
340
  let offset = 0;
@@ -394,11 +418,6 @@ async function experimentResults(resources, experimentId) {
394
418
  }
395
419
  return results;
396
420
  }
397
- async function existingCaseIds(resources, experimentId) {
398
- return new Set((await experimentResults(resources, experimentId))
399
- .map((item) => field(item, "caseId"))
400
- .filter((caseId) => typeof caseId === "string" && caseId.length > 0));
401
- }
402
421
  async function findScorer(resources, name, workspaceId) {
403
422
  const matches = [];
404
423
  let offset = 0;
@@ -434,6 +453,39 @@ function scorerIdentity(value, workspaceId) {
434
453
  }
435
454
  return { id, version };
436
455
  }
456
+ async function scorerDefinition(definition) {
457
+ const outputs = definition.outputs ?? { [definition.name]: { type: "number", minimum: 0, maximum: 1 } };
458
+ if (definition.spec !== undefined)
459
+ return { spec: definition.spec, outputs };
460
+ if (!globalThis.crypto?.subtle)
461
+ throw new Error("Web Crypto is required to identify a scorer");
462
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(String(definition.scorer)));
463
+ const codeDigest = "sha256:" +
464
+ [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
465
+ return {
466
+ outputs,
467
+ spec: {
468
+ executor: "external",
469
+ sdk: "@atlanai/sdk",
470
+ entrypoint: definition.scorer.name || "anonymous",
471
+ code_digest: codeDigest,
472
+ },
473
+ };
474
+ }
475
+ async function reconcileScorer(resources, existing, workspaceId, definition) {
476
+ const identity = scorerIdentity(existing, workspaceId);
477
+ const scorerKind = definition.scorerKind ?? "code";
478
+ const scope = definition.scope ?? "result";
479
+ if (field(existing, "scorerKind") !== scorerKind || field(existing, "scope") !== scope) {
480
+ throw new Error(`scorer '${definition.name}' reuses a Registry name with a different identity`);
481
+ }
482
+ const desired = await scorerDefinition(definition);
483
+ if (canonicalJson(field(existing, "spec")) === canonicalJson(desired.spec) &&
484
+ canonicalJson(field(existing, "outputs")) === canonicalJson(desired.outputs)) {
485
+ return identity;
486
+ }
487
+ return scorerIdentity(await resources.scorers.update(identity.id, desired), workspaceId);
488
+ }
437
489
  async function resolveScorer(resources, workspaceId, evalName, definition) {
438
490
  if (!definition.name || /[\r\n]/.test(definition.name)) {
439
491
  throw new Error("each scorer needs a non-empty single-line name");
@@ -448,7 +500,7 @@ async function resolveScorer(resources, workspaceId, evalName, definition) {
448
500
  if (!Number.isInteger(definition.scorerVersion) || definition.scorerVersion < 1) {
449
501
  throw new Error(`scorer '${definition.name}' has an invalid scorerVersion`);
450
502
  }
451
- return { id: identity.id, version: definition.scorerVersion };
503
+ return scorerIdentity(await resources.scorers.versions.get(identity.id, definition.scorerVersion), workspaceId);
452
504
  }
453
505
  return identity;
454
506
  }
@@ -457,8 +509,10 @@ async function resolveScorer(resources, workspaceId, evalName, definition) {
457
509
  }
458
510
  const artifactName = definition.registryName ?? `${slug(evalName, "eval")}-${slug(definition.name, "score")}`;
459
511
  const existing = await findScorer(resources, artifactName, workspaceId);
460
- if (existing !== undefined)
461
- return scorerIdentity(existing, workspaceId);
512
+ if (existing !== undefined) {
513
+ return reconcileScorer(resources, existing, workspaceId, definition);
514
+ }
515
+ const desired = await scorerDefinition(definition);
462
516
  try {
463
517
  return scorerIdentity(await resources.scorers.create({
464
518
  workspaceId,
@@ -468,12 +522,8 @@ async function resolveScorer(resources, workspaceId, evalName, definition) {
468
522
  sourceId: `atlan-eval:${slug(evalName, "eval")}:${slug(definition.name, "score")}`,
469
523
  scorerKind: definition.scorerKind ?? "code",
470
524
  scope: definition.scope ?? "result",
471
- outputs: definition.outputs ?? { [definition.name]: { type: "number", minimum: 0, maximum: 1 } },
472
- spec: definition.spec ?? {
473
- executor: "external",
474
- sdk: "@atlanai/sdk",
475
- entrypoint: definition.scorer.name || "anonymous",
476
- },
525
+ outputs: desired.outputs,
526
+ spec: desired.spec,
477
527
  }), workspaceId);
478
528
  }
479
529
  catch (error) {
@@ -482,7 +532,7 @@ async function resolveScorer(resources, workspaceId, evalName, definition) {
482
532
  const raced = await findScorer(resources, artifactName, workspaceId);
483
533
  if (raced === undefined)
484
534
  throw error;
485
- return scorerIdentity(raced, workspaceId);
535
+ return reconcileScorer(resources, raced, workspaceId, definition);
486
536
  }
487
537
  }
488
538
  function normalizedScores(value, defaultName) {
@@ -546,12 +596,41 @@ function localSummary(results, durationMs) {
546
596
  },
547
597
  };
548
598
  }
599
+ async function traceSpans(resources, experimentId, traceId) {
600
+ const spans = [];
601
+ const seenCursors = new Set();
602
+ let cursor;
603
+ while (true) {
604
+ const page = await resources.experiments.traces.listSpans({
605
+ experimentId,
606
+ traceId,
607
+ limit: 500,
608
+ fields: "io",
609
+ ...(cursor === undefined ? {} : { cursor }),
610
+ });
611
+ const items = field(page, "items");
612
+ if (Array.isArray(items))
613
+ spans.push(...items);
614
+ const nextCursor = field(field(page, "page"), "nextCursor");
615
+ if (typeof nextCursor !== "string" || nextCursor.length === 0)
616
+ return spans;
617
+ if (seenCursors.has(nextCursor)) {
618
+ throw new Error("experiment trace span pagination repeated a cursor");
619
+ }
620
+ seenCursors.add(nextCursor);
621
+ cursor = nextCursor;
622
+ }
623
+ }
549
624
  async function listedTraceIds(resources, experimentId) {
550
625
  const traceIds = new Set();
551
626
  const seenCursors = new Set();
552
627
  let cursor;
628
+ const windowEnd = Math.floor(Date.now() / 1_000);
553
629
  while (true) {
554
- const page = await resources.experiments.traces.list(experimentId, {
630
+ const page = await resources.experiments.traces.list({
631
+ experimentId,
632
+ startTimeUnixSeconds: windowEnd - 30 * 24 * 60 * 60,
633
+ endTimeUnixSeconds: windowEnd,
555
634
  limit: 500,
556
635
  ...(cursor === undefined ? {} : { cursor }),
557
636
  });
@@ -573,14 +652,73 @@ async function listedTraceIds(resources, experimentId) {
573
652
  cursor = nextCursor;
574
653
  }
575
654
  }
576
- async function verifyTraceJoins(resources, experimentId, expected, timeoutMs) {
655
+ function spanAttributes(span) {
656
+ const attributes = field(span, "attributes");
657
+ return isRecord(attributes) ? attributes : {};
658
+ }
659
+ function missingTraceEvidence(spans, expected) {
660
+ const missing = [];
661
+ const byId = new Map();
662
+ for (const span of spans) {
663
+ const spanId = field(span, "spanId");
664
+ if (typeof spanId === "string")
665
+ byId.set(spanId, span);
666
+ }
667
+ const root = expected.rootSpanId === undefined
668
+ ? spans.find((span) => spanAttributes(span)["atlan.eval.case_id"] === expected.caseId &&
669
+ spanAttributes(span)["atlan.eval.evidence_complete"] === true)
670
+ : byId.get(expected.rootSpanId);
671
+ if (root === undefined) {
672
+ missing.push("case root");
673
+ }
674
+ else {
675
+ const attributes = spanAttributes(root);
676
+ if (attributes["atlan.eval.evidence_complete"] !== true) {
677
+ missing.push("producer completion marker");
678
+ }
679
+ if (attributes["atlan.eval.case_id"] !== expected.caseId)
680
+ missing.push("case identity");
681
+ if (expected.datasetRecordId !== undefined &&
682
+ attributes["atlan.eval.dataset_record_id"] !== expected.datasetRecordId) {
683
+ missing.push("dataset record identity");
684
+ }
685
+ }
686
+ const absentIds = [...expected.requiredSpanIds].filter((spanId) => !byId.has(spanId));
687
+ if (absentIds.length > 0)
688
+ missing.push(`${absentIds.length} SDK-owned spans`);
689
+ if (spans.some((span) => spanAttributes(span)["atlan.eval.evidence_truncated"] === true)) {
690
+ missing.push("untruncated payloads");
691
+ }
692
+ for (const [scoreName, scorer] of expected.scores) {
693
+ const present = spans.some((span) => field(span, "scoreName") === scoreName &&
694
+ field(span, "scorerId") === scorer.id &&
695
+ field(span, "scorerVersion") === scorer.version);
696
+ if (!present)
697
+ missing.push(`score '${scoreName}'`);
698
+ }
699
+ return missing;
700
+ }
701
+ async function verifyTraceEvidence(resources, experimentId, expected, timeoutMs) {
577
702
  const deadline = Date.now() + timeoutMs;
578
- let missing = expected;
703
+ let missing = new Map();
579
704
  let lastError;
580
705
  while (true) {
581
706
  try {
582
- const observed = await listedTraceIds(resources, experimentId);
583
- missing = new Set([...expected].filter((traceId) => !observed.has(traceId)));
707
+ missing = new Map();
708
+ if (typeof resources.experiments.traces.listSpans === "function") {
709
+ for (const [traceId, expectation] of expected) {
710
+ const gaps = missingTraceEvidence(await traceSpans(resources, experimentId, traceId), expectation);
711
+ if (gaps.length > 0)
712
+ missing.set(traceId, gaps);
713
+ }
714
+ }
715
+ else {
716
+ const observed = await listedTraceIds(resources, experimentId);
717
+ for (const traceId of expected.keys()) {
718
+ if (!observed.has(traceId))
719
+ missing.set(traceId, ["trace read"]);
720
+ }
721
+ }
584
722
  lastError = undefined;
585
723
  }
586
724
  catch (error) {
@@ -588,15 +726,16 @@ async function verifyTraceJoins(resources, experimentId, expected, timeoutMs) {
588
726
  ![404, 429, 500, 502, 503, 504].includes(error.status)) {
589
727
  throw error;
590
728
  }
591
- missing = expected;
729
+ missing = new Map([...expected.keys()].map((traceId) => [traceId, ["trace read"]]));
592
730
  lastError = error;
593
731
  }
594
732
  if (missing.size === 0)
595
733
  return;
596
734
  const remaining = deadline - Date.now();
597
735
  if (remaining <= 0) {
598
- const failure = new Error(`Registry did not expose ${missing.size} of ${expected.size} case traces ` +
599
- "before the verification deadline");
736
+ const failure = new Error(`Registry did not expose complete trace evidence for ${missing.size} of ` +
737
+ `${expected.size} case traces before the verification deadline: ` +
738
+ [...missing].map(([traceId, gaps]) => `${traceId} (${gaps.join(", ")})`).join("; "));
600
739
  if (lastError !== undefined)
601
740
  failure.cause = lastError;
602
741
  throw failure;
@@ -632,6 +771,11 @@ function defaultConnections(name, options) {
632
771
  if (sampleRate !== undefined && sampleRate !== 1) {
633
772
  throw new Error("Eval tracing requires sampleRate=1 so no case trace is sampled out");
634
773
  }
774
+ const traceContent = traceLogger.client.config
775
+ ?.traceContent;
776
+ if (traceContent === false) {
777
+ throw new Error("Eval tracing requires traceContent=true so case evidence is readable");
778
+ }
635
779
  return { client, logger: traceLogger, workspaceId, tracing };
636
780
  }
637
781
  /**
@@ -689,11 +833,14 @@ async function Eval(name, evaluator, options = {}) {
689
833
  if (field(experiment, "experimentStatus") !== "running") {
690
834
  throw new Error("resumeExperimentId must name a running experiment");
691
835
  }
692
- if (options.contextManifest !== undefined &&
693
- field(field(experiment, "config"), "context_manifest_digest") !==
694
- options.contextManifest.digest) {
836
+ const storedConfig = field(experiment, "config") ?? {};
837
+ if (field(storedConfig, "context_manifest_digest") !== options.contextManifest?.digest) {
695
838
  throw new Error("resume experiment uses a different context manifest");
696
839
  }
840
+ const storedUserConfig = Object.fromEntries(Object.entries(storedConfig).filter(([key]) => !["runner", "registry_dataset", "context_manifest", "context_manifest_digest", "eval_cases_digest"].includes(key)));
841
+ if (canonicalJson(storedUserConfig) !== canonicalJson(options.config ?? {})) {
842
+ throw new Error("resume experiment uses a different config");
843
+ }
697
844
  let resolvedDataset;
698
845
  if (evaluator.dataset !== undefined) {
699
846
  resolvedDataset = await resolveDataset(client, evaluator.dataset, workspaceId);
@@ -706,6 +853,9 @@ async function Eval(name, evaluator, options = {}) {
706
853
  }
707
854
  else {
708
855
  cases = await collectData(evaluator.data);
856
+ if (field(storedConfig, "eval_cases_digest") !== await casesDigest(cases)) {
857
+ throw new Error("resume inline data differs or has no saved digest; start a new experiment");
858
+ }
709
859
  }
710
860
  run = new EvalRun(experiment, resolvedDataset, options.contextManifest);
711
861
  }
@@ -717,6 +867,7 @@ async function Eval(name, evaluator, options = {}) {
717
867
  else {
718
868
  cases = await collectData(evaluator.data);
719
869
  const config = { ...experimentBody.config };
870
+ config.eval_cases_digest = await casesDigest(cases);
720
871
  if (options.contextManifest !== undefined) {
721
872
  config.context_manifest = options.contextManifest;
722
873
  config.context_manifest_digest = options.contextManifest.digest;
@@ -729,23 +880,34 @@ async function Eval(name, evaluator, options = {}) {
729
880
  }
730
881
  }
731
882
  const scorerCache = new Map();
883
+ const traceEvidence = new Map();
732
884
  const started = Date.now();
733
885
  const results = [];
734
886
  try {
735
887
  options.onStart?.({ experimentId: run.id, experiment: run.experiment });
736
888
  if (evaluator.dataset !== undefined) {
889
+ const snapshot = snapshotCases(run.experiment);
890
+ if (options.resumeExperimentId !== undefined && snapshot === undefined) {
891
+ throw new Error("resume requires the experiment's immutable dataset snapshot");
892
+ }
737
893
  cases =
738
- snapshotCases(run.experiment) ??
894
+ snapshot ??
739
895
  (await datasetCases(resources, String(field(run.dataset, "id"))));
740
896
  }
741
- const completedCaseIds = options.resumeExperimentId === undefined
742
- ? new Set()
743
- : await existingCaseIds(resources, run.id);
897
+ const caseIds = new Set(cases.map((item, index) => item.id ?? `case-${index + 1}`));
898
+ if (caseIds.size !== cases.length)
899
+ throw new Error("Eval case IDs must be unique");
900
+ const persisted = options.resumeExperimentId === undefined ? [] : await experimentResults(resources, run.id);
901
+ const completedCaseIds = new Set(persisted.map((item) => field(item, "caseId")));
902
+ if (completedCaseIds.size !== persisted.length || [...completedCaseIds].some((id) => typeof id !== "string" || !caseIds.has(id))) {
903
+ throw new Error("resume experiment contains unexpected or duplicate case IDs");
904
+ }
744
905
  for (const definition of evaluator.scores) {
745
906
  scorerCache.set(definition.name, await resolveScorer(resources, workspaceId, name, definition));
746
907
  }
747
908
  for (const [index, item] of cases.entries()) {
748
909
  const caseId = item.id ?? `case-${index + 1}`;
910
+ const datasetRecordId = evaluator.dataset === undefined ? undefined : item.id;
749
911
  if (completedCaseIds.has(caseId))
750
912
  continue;
751
913
  const caseStarted = Date.now();
@@ -753,8 +915,15 @@ async function Eval(name, evaluator, options = {}) {
753
915
  const failures = [];
754
916
  let output;
755
917
  let traceId = "";
918
+ let rootSpanId;
919
+ const requiredSpanIds = new Set();
920
+ const scoreEvidence = new Map();
756
921
  const metadata = (item.metadata ?? {});
757
- await tracing.propagateAttributes(run.traceOptions, () => traceLogger.client.startAsCurrentSpan(`eval.case.${index + 1}`, {
922
+ await tracing.propagateAttributes({
923
+ ...run.traceOptions,
924
+ caseId,
925
+ ...(datasetRecordId === undefined ? {} : { datasetRecordId }),
926
+ }, () => traceLogger.client.startAsCurrentSpan(`eval.case.${index + 1}`, {
758
927
  asType: "task",
759
928
  fields: {
760
929
  input: item.input,
@@ -763,8 +932,13 @@ async function Eval(name, evaluator, options = {}) {
763
932
  },
764
933
  }, async (rootSpan) => {
765
934
  traceId = rootSpan.traceId;
935
+ rootSpanId = rootSpan.spanId;
936
+ if (rootSpan.spanId !== undefined)
937
+ requiredSpanIds.add(rootSpan.spanId);
766
938
  try {
767
939
  output = await traceLogger.client.startAsCurrentSpan("eval.task", { asType: "task", fields: { input: item.input } }, async (taskSpan) => {
940
+ if (taskSpan.spanId !== undefined)
941
+ requiredSpanIds.add(taskSpan.spanId);
768
942
  const value = await evaluator.task(item.input, {
769
943
  expected: item.expected,
770
944
  metadata,
@@ -779,6 +953,7 @@ async function Eval(name, evaluator, options = {}) {
779
953
  catch (error) {
780
954
  failures.push(safeError(error));
781
955
  rootSpan.recordFailure(error);
956
+ rootSpan.markEvidenceComplete?.();
782
957
  return;
783
958
  }
784
959
  for (const definition of evaluator.scores) {
@@ -793,6 +968,8 @@ async function Eval(name, evaluator, options = {}) {
793
968
  },
794
969
  },
795
970
  }, async (scoreSpan) => {
971
+ if (scoreSpan.spanId !== undefined)
972
+ requiredSpanIds.add(scoreSpan.spanId);
796
973
  const value = await definition.scorer({ ...item, output: output });
797
974
  scoreSpan.update({ output: value });
798
975
  return value;
@@ -813,6 +990,7 @@ async function Eval(name, evaluator, options = {}) {
813
990
  }
814
991
  const numeric = numericScore(scored.score);
815
992
  scores[scoreName] = numeric;
993
+ scoreEvidence.set(scoreName, registered);
816
994
  rootSpan.score(scoreName, numeric, {
817
995
  scorerId: registered.id,
818
996
  scorerVersion: registered.version,
@@ -828,6 +1006,7 @@ async function Eval(name, evaluator, options = {}) {
828
1006
  }
829
1007
  }
830
1008
  rootSpan.update({ output, metadata });
1009
+ rootSpan.markEvidenceComplete?.();
831
1010
  }));
832
1011
  if (!TRACE_ID.test(traceId) || /^0+$/.test(traceId)) {
833
1012
  throw new Error(`case ${index + 1} did not produce a trace ID`);
@@ -841,41 +1020,84 @@ async function Eval(name, evaluator, options = {}) {
841
1020
  traceId,
842
1021
  durationMs: Date.now() - caseStarted,
843
1022
  });
844
- }
845
- // Score aggregation reads spans, so make the async exporter observable first.
846
- await traceLogger.flush();
847
- if (options.verifyTraces !== false && results.length > 0) {
848
- await verifyTraceJoins(resources, run.id, new Set(results.map((result) => result.traceId)), traceVerificationTimeoutMs);
849
- }
850
- const resultBodies = results.map((result, index) => ({
851
- workspaceId,
852
- name: `${slug(run.id, "experiment")}-case-${index + 1}`,
853
- caseId: result.caseId,
854
- datasetRecordId: result.id || undefined,
855
- input: registryObject(result.input),
856
- expected: registryObject(result.expected),
857
- output: registryObject(result.output),
858
- error: result.error,
859
- scores: result.scores,
860
- traceId: result.traceId,
861
- durationMs: result.durationMs,
862
- sourceType: "local",
863
- }));
864
- for (let offset = 0; offset < resultBodies.length; offset += 100) {
1023
+ traceEvidence.set(traceId, {
1024
+ caseId,
1025
+ ...(datasetRecordId === undefined ? {} : { datasetRecordId }),
1026
+ ...(rootSpanId === undefined ? {} : { rootSpanId }),
1027
+ requiredSpanIds,
1028
+ scores: scoreEvidence,
1029
+ });
1030
+ // Persist each completed case before starting another costly task.
1031
+ // Keep the source index so a resumed subset cannot reuse earlier names.
1032
+ const result = results[results.length - 1];
1033
+ await traceLogger.flush();
1034
+ if (options.verifyTraces !== false) {
1035
+ await verifyTraceEvidence(resources, run.id, new Map([[result.traceId, traceEvidence.get(result.traceId)]]), traceVerificationTimeoutMs);
1036
+ }
1037
+ const resultBody = {
1038
+ workspaceId,
1039
+ name: `${slug(run.id, "experiment")}-case-${index + 1}`,
1040
+ caseId: result.caseId,
1041
+ datasetRecordId,
1042
+ input: registryObject(result.input),
1043
+ expected: registryObject(result.expected),
1044
+ output: registryObject(result.output),
1045
+ error: result.error,
1046
+ scores: result.scores,
1047
+ traceId: result.traceId,
1048
+ durationMs: result.durationMs,
1049
+ sourceType: "local",
1050
+ };
865
1051
  const response = await resources.experiments.results.createBulk(run.id, {
866
- items: resultBodies.slice(offset, offset + 100),
1052
+ items: [resultBody],
867
1053
  });
868
1054
  const slots = field(response, "items");
869
- if (Array.isArray(slots)) {
870
- const failed = slots.filter((slot) => {
871
- const status = field(slot, "statusCode");
872
- return typeof status === "number" && status >= 400;
873
- });
874
- if (failed.length > 0)
875
- throw new Error(`${failed.length} Eval results were rejected`);
1055
+ const status = Array.isArray(slots) && slots.length === 1 ? field(slots[0], "statusCode") : undefined;
1056
+ if (typeof status !== "number" || status < 200 || status >= 300) {
1057
+ throw new Error("1 Eval results were rejected or not acknowledged");
876
1058
  }
877
1059
  }
878
1060
  const persistedResults = await experimentResults(resources, run.id);
1061
+ const persistedCaseIds = new Set(persistedResults.map((item) => field(item, "caseId")));
1062
+ if (persistedResults.length !== caseIds.size || persistedCaseIds.size !== caseIds.size || [...caseIds].some((id) => !persistedCaseIds.has(id))) {
1063
+ throw new Error("Registry result coverage does not match the evaluation cases");
1064
+ }
1065
+ if (options.verifyTraces !== false && persistedResults.length > 0) {
1066
+ const traceIds = new Set();
1067
+ for (const item of persistedResults) {
1068
+ const traceId = field(item, "traceId");
1069
+ if (typeof traceId !== "string" || !TRACE_ID.test(traceId) || /^0+$/.test(traceId)) {
1070
+ throw new Error("Registry result is missing a valid trace ID");
1071
+ }
1072
+ traceIds.add(traceId);
1073
+ }
1074
+ const expectations = new Map();
1075
+ for (const item of persistedResults) {
1076
+ const traceId = field(item, "traceId");
1077
+ const current = traceEvidence.get(traceId);
1078
+ if (current !== undefined) {
1079
+ expectations.set(traceId, current);
1080
+ continue;
1081
+ }
1082
+ const persistedScores = field(item, "scores");
1083
+ const scorePins = new Map();
1084
+ if (isRecord(persistedScores)) {
1085
+ for (const [scoreName, value] of Object.entries(persistedScores)) {
1086
+ const scorer = scorerCache.get(scoreName);
1087
+ if (value !== null && scorer !== undefined)
1088
+ scorePins.set(scoreName, scorer);
1089
+ }
1090
+ }
1091
+ const datasetRecordId = field(item, "datasetRecordId");
1092
+ expectations.set(traceId, {
1093
+ caseId: String(field(item, "caseId")),
1094
+ ...(typeof datasetRecordId === "string" ? { datasetRecordId } : {}),
1095
+ requiredSpanIds: new Set(),
1096
+ scores: scorePins,
1097
+ });
1098
+ }
1099
+ await verifyTraceEvidence(resources, run.id, expectations, traceVerificationTimeoutMs);
1100
+ }
879
1101
  const local = localSummary(persistedResults, Date.now() - started);
880
1102
  const experiment = await resources.experiments.update(run.id, {
881
1103
  experimentStatus: "completed",
@@ -902,15 +1124,8 @@ async function Eval(name, evaluator, options = {}) {
902
1124
  catch {
903
1125
  // Preserve the original runner/upload failure.
904
1126
  }
905
- try {
906
- await resources.experiments.update(run.id, {
907
- experimentStatus: "failed",
908
- summary: localSummary(results, Date.now() - started),
909
- });
910
- }
911
- catch {
912
- // Preserve the original runner/upload failure.
913
- }
1127
+ // Leave interrupted infrastructure work running: terminal experiments
1128
+ // cannot accept more results. Resume reconciles Registry case IDs.
914
1129
  throw error;
915
1130
  }
916
1131
  }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { AtlanClient, ServiceNamespace, createService } from "./client";
2
2
  export type { AtlanClientOptions, ForwardedOperation, Service } from "./client";
3
3
  export { buildResources, resourceMap } from "./resources";
4
4
  export type { Resource, ResourceOperation } from "./resources";
5
+ export type { ResourceMap, ResourceRoots } from "./resource-types";
5
6
  export { AtlanAPIError } from "./errors";
6
7
  export { operationManifest, operations } from "./operations";
7
8
  export type { Operation, OperationManifest, ServiceContract } from "./operations";