@ai-sdk/harness 1.0.91 → 1.0.93

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.
@@ -233,6 +233,16 @@ declare function getAiGatewayAuthFromEnv({ env, }: {
233
233
  baseUrl: string;
234
234
  };
235
235
 
236
+ /**
237
+ * Authentication-related environment variables supplied directly to a harness.
238
+ *
239
+ * When used as an adapter's `auth` option, this environment replaces the host
240
+ * process environment for authentication discovery.
241
+ */
242
+ type HarnessV1AuthenticationEnvironment = Readonly<Record<string, string>>;
243
+
244
+ declare function isHarnessAuthenticationEnvironment(value: unknown): value is HarnessV1AuthenticationEnvironment;
245
+
236
246
  /**
237
247
  * Connection details for a sandbox-exposed port. Headers are scoped to the
238
248
  * returned URL and must be included when opening the connection.
@@ -521,7 +531,13 @@ type WriteSkillsOptions = {
521
531
  }) => string;
522
532
  trailingNewline?: boolean;
523
533
  };
524
- declare function writeSkills({ sandbox, rootDir, skills, abortSignal, skillNamePattern, invalidSkillNameMessage, filePathMode, invalidSkillFilePathMessage, trailingNewline, }: WriteSkillsOptions): Promise<void>;
534
+ type WriteSkillsResult = {
535
+ changed: boolean;
536
+ written: string[];
537
+ removed: string[];
538
+ unchanged: string[];
539
+ };
540
+ declare function writeSkills({ sandbox, rootDir, skills, abortSignal, skillNamePattern, invalidSkillNameMessage, filePathMode, invalidSkillFilePathMessage, trailingNewline, }: WriteSkillsOptions): Promise<WriteSkillsResult>;
525
541
 
526
542
  type BridgeReadySource = 'stdout' | 'metadata';
527
543
  type BridgeReadyErrorContext = {
@@ -591,4 +607,4 @@ declare function resolveSandboxDefaultWorkingDirectory({ sandboxSession, abortSi
591
607
 
592
608
  declare function getRestrictedSandboxSession(sandboxSession: HarnessV1NetworkSandboxSession | Experimental_SandboxSession): Experimental_SandboxSession;
593
609
 
594
- export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, type Experimental_BridgeUserMessageRequest, type Experimental_BridgeUserMessageResponse, type Experimental_BridgeUserMessageSubmitter, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteSkillsOptions, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createCredentialRequestTransformation, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, writeSkills };
610
+ export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, type Experimental_BridgeUserMessageRequest, type Experimental_BridgeUserMessageResponse, type Experimental_BridgeUserMessageSubmitter, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteSkillsOptions, type WriteSkillsResult, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createCredentialRequestTransformation, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isHarnessAuthenticationEnvironment, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, writeSkills };
@@ -437,6 +437,19 @@ function getAiGatewayAuthFromEnv({
437
437
  };
438
438
  }
439
439
 
440
+ // src/utils/authentication-environment.ts
441
+ function isHarnessAuthenticationEnvironment(value) {
442
+ if (value == null || typeof value !== "object") {
443
+ return false;
444
+ }
445
+ if (Array.isArray(value) || Object.values(value).some((entry) => typeof entry !== "string")) {
446
+ throw new TypeError(
447
+ "Invalid auth: expected an authentication mode or a flat record with string values."
448
+ );
449
+ }
450
+ return true;
451
+ }
452
+
440
453
  // src/utils/sandbox-credential-brokering.ts
441
454
  import { randomBytes } from "crypto";
442
455
  var SANDBOX_CREDENTIAL_PLACEHOLDER_PREFIX = "aisdkhc_";
@@ -547,7 +560,14 @@ function shellQuote(value) {
547
560
  }
548
561
 
549
562
  // src/utils/write-skills.ts
563
+ import { createHash } from "crypto";
550
564
  import path2 from "path";
565
+ import {
566
+ safeParseJSON as safeParseJSON3
567
+ } from "@ai-sdk/provider-utils";
568
+ var SKILLS_MANIFEST_FILENAME = ".ai-sdk-harness-skills.json";
569
+ var SKILLS_MANIFEST_VERSION = 1;
570
+ var SAFE_MANIFEST_SKILL_NAME = /^[A-Za-z0-9._-]+$/;
551
571
  async function writeSkills({
552
572
  sandbox,
553
573
  rootDir,
@@ -559,60 +579,333 @@ async function writeSkills({
559
579
  invalidSkillFilePathMessage = ({ skillName, filePath }) => `Invalid skill file path for ${skillName}: ${filePath}`,
560
580
  trailingNewline = false
561
581
  }) {
562
- var _a, _b;
563
- for (const skill of skills) {
564
- validateSkillName({
565
- name: skill.name,
566
- pattern: skillNamePattern,
567
- message: invalidSkillNameMessage
582
+ var _a;
583
+ const projectedSkills = skills.map(
584
+ (skill) => projectSkill({
585
+ skill,
586
+ skillNamePattern,
587
+ invalidSkillNameMessage,
588
+ filePathMode,
589
+ invalidSkillFilePathMessage,
590
+ trailingNewline
591
+ })
592
+ ).sort((a, b) => a.name.localeCompare(b.name));
593
+ assertUniqueSkillNames(projectedSkills);
594
+ const manifestPath = path2.posix.join(rootDir, SKILLS_MANIFEST_FILENAME);
595
+ const existingManifest = await readSkillsManifest({
596
+ sandbox,
597
+ manifestPath,
598
+ abortSignal
599
+ });
600
+ const nextEntries = projectedSkills.map(({ name, hash }) => ({ name, hash }));
601
+ const nextByName = new Map(nextEntries.map((entry) => [entry.name, entry]));
602
+ if ((existingManifest == null ? void 0 : existingManifest.state) === "pending") {
603
+ await ensureSkillsDirectory({ sandbox, rootDir, abortSignal });
604
+ const recoveryNames = existingManifest.skills.map((skill) => skill.name);
605
+ await removeSkillDirectories({
606
+ sandbox,
607
+ rootDir,
608
+ skillNames: recoveryNames,
609
+ abortSignal
568
610
  });
569
- for (const file of (_a = skill.files) != null ? _a : []) {
611
+ const removed2 = recoveryNames.filter((name) => !nextByName.has(name)).sort((a, b) => a.localeCompare(b));
612
+ await writeProjectedSkills({
613
+ sandbox,
614
+ rootDir,
615
+ skills: projectedSkills,
616
+ abortSignal
617
+ });
618
+ await writeSkillsManifest({
619
+ sandbox,
620
+ manifestPath,
621
+ manifest: {
622
+ version: SKILLS_MANIFEST_VERSION,
623
+ state: "complete",
624
+ skills: nextEntries
625
+ },
626
+ abortSignal
627
+ });
628
+ return {
629
+ changed: recoveryNames.length > 0 || projectedSkills.length > 0,
630
+ written: projectedSkills.map((skill) => skill.name),
631
+ removed: removed2,
632
+ unchanged: []
633
+ };
634
+ }
635
+ const previousEntries = (_a = existingManifest == null ? void 0 : existingManifest.skills) != null ? _a : [];
636
+ const previousByName = new Map(
637
+ previousEntries.map((entry) => [entry.name, entry])
638
+ );
639
+ const removed = previousEntries.filter((entry) => !nextByName.has(entry.name)).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
640
+ const written = projectedSkills.filter((skill) => {
641
+ var _a2;
642
+ return ((_a2 = previousByName.get(skill.name)) == null ? void 0 : _a2.hash) !== skill.hash;
643
+ }).map((skill) => skill.name).sort((a, b) => a.localeCompare(b));
644
+ const unchanged = projectedSkills.filter((skill) => {
645
+ var _a2;
646
+ return ((_a2 = previousByName.get(skill.name)) == null ? void 0 : _a2.hash) === skill.hash;
647
+ }).map((skill) => skill.name).sort((a, b) => a.localeCompare(b));
648
+ const changed = removed.length > 0 || written.length > 0;
649
+ if (!changed && existingManifest != null) {
650
+ return { changed: false, written, removed, unchanged };
651
+ }
652
+ const previouslyOwned = new Set(previousEntries.map((entry) => entry.name));
653
+ for (const skillName of written) {
654
+ if (previouslyOwned.has(skillName)) continue;
655
+ await assertSkillDirectoryAvailable({
656
+ sandbox,
657
+ rootDir,
658
+ skillName,
659
+ abortSignal
660
+ });
661
+ }
662
+ await ensureSkillsDirectory({ sandbox, rootDir, abortSignal });
663
+ const pendingNames = Array.from(
664
+ /* @__PURE__ */ new Set([
665
+ ...previousEntries.map((entry) => entry.name),
666
+ ...nextEntries.map((entry) => entry.name)
667
+ ])
668
+ ).sort((a, b) => a.localeCompare(b));
669
+ await writeSkillsManifest({
670
+ sandbox,
671
+ manifestPath,
672
+ manifest: {
673
+ version: SKILLS_MANIFEST_VERSION,
674
+ state: "pending",
675
+ skills: pendingNames.map((name) => {
676
+ var _a2, _b;
677
+ return {
678
+ name,
679
+ hash: (_b = (_a2 = nextByName.get(name)) == null ? void 0 : _a2.hash) != null ? _b : previousByName.get(name).hash
680
+ };
681
+ })
682
+ },
683
+ abortSignal
684
+ });
685
+ await removeSkillDirectories({
686
+ sandbox,
687
+ rootDir,
688
+ skillNames: [
689
+ ...removed,
690
+ ...written.filter((name) => previouslyOwned.has(name))
691
+ ],
692
+ abortSignal
693
+ });
694
+ const writtenSet = new Set(written);
695
+ await writeProjectedSkills({
696
+ sandbox,
697
+ rootDir,
698
+ skills: projectedSkills.filter((skill) => writtenSet.has(skill.name)),
699
+ abortSignal
700
+ });
701
+ await writeSkillsManifest({
702
+ sandbox,
703
+ manifestPath,
704
+ manifest: {
705
+ version: SKILLS_MANIFEST_VERSION,
706
+ state: "complete",
707
+ skills: nextEntries
708
+ },
709
+ abortSignal
710
+ });
711
+ return { changed, written, removed, unchanged };
712
+ }
713
+ async function ensureSkillsDirectory({
714
+ sandbox,
715
+ rootDir,
716
+ abortSignal
717
+ }) {
718
+ await runSandboxCommand({
719
+ sandbox,
720
+ command: `mkdir -p ${shellQuote(rootDir)}`,
721
+ abortSignal,
722
+ errorMessage: `Failed to create skills directory: ${rootDir}`
723
+ });
724
+ }
725
+ function projectSkill({
726
+ skill,
727
+ skillNamePattern,
728
+ invalidSkillNameMessage,
729
+ filePathMode,
730
+ invalidSkillFilePathMessage,
731
+ trailingNewline
732
+ }) {
733
+ var _a;
734
+ const name = validateSkillName({
735
+ name: skill.name,
736
+ pattern: skillNamePattern,
737
+ message: invalidSkillNameMessage
738
+ });
739
+ const files = /* @__PURE__ */ new Map();
740
+ files.set("SKILL.md", renderSkillFile({ skill, trailingNewline }));
741
+ for (const file of (_a = skill.files) != null ? _a : []) {
742
+ files.set(
570
743
  normalizeSkillFilePath({
571
744
  skillName: skill.name,
572
745
  filePath: file.path,
573
746
  mode: filePathMode,
574
747
  message: invalidSkillFilePathMessage
575
- });
748
+ }),
749
+ file.content
750
+ );
751
+ }
752
+ const projectedFiles = Array.from(files, ([filePath, content]) => ({
753
+ path: filePath,
754
+ content
755
+ })).sort((a, b) => a.path.localeCompare(b.path));
756
+ const hash = createHash("sha256");
757
+ for (const file of projectedFiles) {
758
+ hash.update(String(Buffer.byteLength(file.path)));
759
+ hash.update(":");
760
+ hash.update(file.path);
761
+ hash.update(String(Buffer.byteLength(file.content)));
762
+ hash.update(":");
763
+ hash.update(file.content);
764
+ }
765
+ return { name, hash: hash.digest("hex"), files: projectedFiles };
766
+ }
767
+ async function readSkillsManifest({
768
+ sandbox,
769
+ manifestPath,
770
+ abortSignal
771
+ }) {
772
+ const content = await sandbox.readTextFile({
773
+ path: manifestPath,
774
+ abortSignal
775
+ });
776
+ if (content == null) return void 0;
777
+ const parsed = await safeParseJSON3({ text: content });
778
+ if (!parsed.success || !isSkillsManifest(parsed.value)) {
779
+ throw new Error(`Invalid AI SDK harness skills manifest: ${manifestPath}`);
780
+ }
781
+ return parsed.value;
782
+ }
783
+ function isSkillsManifest(value) {
784
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
785
+ return false;
786
+ }
787
+ const manifest = value;
788
+ if (manifest.version !== SKILLS_MANIFEST_VERSION || manifest.state !== "complete" && manifest.state !== "pending" || !Array.isArray(manifest.skills)) {
789
+ return false;
790
+ }
791
+ const names = /* @__PURE__ */ new Set();
792
+ for (const entry of manifest.skills) {
793
+ if (entry == null || typeof entry !== "object" || Array.isArray(entry)) {
794
+ return false;
576
795
  }
796
+ const skill = entry;
797
+ if (typeof skill.name !== "string" || !isSafeManifestSkillName(skill.name) || typeof skill.hash !== "string" || !/^[a-f0-9]{64}$/.test(skill.hash) || names.has(skill.name)) {
798
+ return false;
799
+ }
800
+ names.add(skill.name);
577
801
  }
578
- await sandbox.run({
579
- command: `mkdir -p ${shellQuote(rootDir)}`,
802
+ return true;
803
+ }
804
+ async function writeSkillsManifest({
805
+ sandbox,
806
+ manifestPath,
807
+ manifest,
808
+ abortSignal
809
+ }) {
810
+ const temporaryPath = `${manifestPath}.tmp`;
811
+ await sandbox.writeTextFile({
812
+ path: temporaryPath,
813
+ content: `${JSON.stringify(manifest, null, 2)}
814
+ `,
580
815
  abortSignal
581
816
  });
817
+ await runSandboxCommand({
818
+ sandbox,
819
+ command: `mv -f ${shellQuote(temporaryPath)} ${shellQuote(manifestPath)}`,
820
+ abortSignal,
821
+ errorMessage: `Failed to update skills manifest: ${manifestPath}`
822
+ });
823
+ }
824
+ async function assertSkillDirectoryAvailable({
825
+ sandbox,
826
+ rootDir,
827
+ skillName,
828
+ abortSignal
829
+ }) {
830
+ const skillDir = path2.posix.join(rootDir, skillName);
831
+ const result = await sandbox.run({
832
+ command: `test ! -e ${shellQuote(skillDir)}`,
833
+ abortSignal
834
+ });
835
+ if (result.exitCode !== 0) {
836
+ throw new Error(
837
+ `Cannot write harness skill '${skillName}': ${skillDir} already exists and is not owned by the AI SDK harness.`
838
+ );
839
+ }
840
+ }
841
+ async function removeSkillDirectories({
842
+ sandbox,
843
+ rootDir,
844
+ skillNames,
845
+ abortSignal
846
+ }) {
847
+ if (skillNames.length === 0) return;
848
+ const directories = Array.from(new Set(skillNames)).sort((a, b) => a.localeCompare(b)).map((name) => shellQuote(path2.posix.join(rootDir, name))).join(" ");
849
+ await runSandboxCommand({
850
+ sandbox,
851
+ command: `rm -rf -- ${directories}`,
852
+ abortSignal,
853
+ errorMessage: `Failed to replace harness skills in: ${rootDir}`
854
+ });
855
+ }
856
+ async function writeProjectedSkills({
857
+ sandbox,
858
+ rootDir,
859
+ skills,
860
+ abortSignal
861
+ }) {
582
862
  for (const skill of skills) {
583
- const name = validateSkillName({
584
- name: skill.name,
585
- pattern: skillNamePattern,
586
- message: invalidSkillNameMessage
587
- });
588
- const skillDir = path2.posix.join(rootDir, name);
589
- await sandbox.writeTextFile({
590
- path: path2.posix.join(skillDir, "SKILL.md"),
591
- content: renderSkillFile({ skill, trailingNewline }),
592
- abortSignal
593
- });
594
- for (const file of (_b = skill.files) != null ? _b : []) {
595
- const filePath = normalizeSkillFilePath({
596
- skillName: skill.name,
597
- filePath: file.path,
598
- mode: filePathMode,
599
- message: invalidSkillFilePathMessage
600
- });
863
+ const skillDir = path2.posix.join(rootDir, skill.name);
864
+ for (const file of skill.files) {
601
865
  await sandbox.writeTextFile({
602
- path: path2.posix.join(skillDir, filePath),
866
+ path: path2.posix.join(skillDir, file.path),
603
867
  content: file.content,
604
868
  abortSignal
605
869
  });
606
870
  }
607
871
  }
608
872
  }
873
+ async function runSandboxCommand({
874
+ sandbox,
875
+ command,
876
+ abortSignal,
877
+ errorMessage
878
+ }) {
879
+ const result = await sandbox.run({ command, abortSignal });
880
+ if (result.exitCode !== 0) {
881
+ throw new Error(
882
+ `${errorMessage} (exit ${result.exitCode})${result.stderr ? `: ${result.stderr}` : ""}`
883
+ );
884
+ }
885
+ }
886
+ function assertUniqueSkillNames(skills) {
887
+ for (let index = 1; index < skills.length; index++) {
888
+ if (skills[index - 1].name === skills[index].name) {
889
+ throw new Error(`Duplicate skill name: ${skills[index].name}`);
890
+ }
891
+ }
892
+ }
893
+ function isSafeManifestSkillName(name) {
894
+ SAFE_MANIFEST_SKILL_NAME.lastIndex = 0;
895
+ const matches = SAFE_MANIFEST_SKILL_NAME.test(name);
896
+ SAFE_MANIFEST_SKILL_NAME.lastIndex = 0;
897
+ return matches && name !== "." && name !== ".." && !name.includes("/");
898
+ }
609
899
  function validateSkillName({
610
900
  name,
611
901
  pattern,
612
902
  message
613
903
  }) {
614
904
  var _a;
615
- if (!pattern.test(name) || name === "." || name === "..") {
905
+ pattern.lastIndex = 0;
906
+ const matches = pattern.test(name);
907
+ pattern.lastIndex = 0;
908
+ if (!matches || name === "." || name === "..") {
616
909
  throw new Error((_a = message == null ? void 0 : message({ name })) != null ? _a : `Invalid skill name: ${name}`);
617
910
  }
618
911
  return name;
@@ -649,7 +942,7 @@ ${skill.content}`;
649
942
 
650
943
  // src/utils/bridge-ready.ts
651
944
  import {
652
- safeParseJSON as safeParseJSON3
945
+ safeParseJSON as safeParseJSON4
653
946
  } from "@ai-sdk/provider-utils";
654
947
  import { z as z4 } from "zod/v4";
655
948
 
@@ -1114,7 +1407,7 @@ async function waitForBridgeReady({
1114
1407
  if (value === void 0) continue;
1115
1408
  for (const line of decoder.push(value)) {
1116
1409
  pushTail({ lines: stdoutTail, line });
1117
- const parsed = await safeParseJSON3({
1410
+ const parsed = await safeParseJSON4({
1118
1411
  text: line,
1119
1412
  schema: harnessV1BridgeReadySchema
1120
1413
  });
@@ -1151,7 +1444,7 @@ async function readBridgeMetaReady({
1151
1444
  })
1152
1445
  ).catch(() => null);
1153
1446
  if (raw == null) return void 0;
1154
- const parsed = await safeParseJSON3({ text: raw, schema: bridgeMetaSchema });
1447
+ const parsed = await safeParseJSON4({ text: raw, schema: bridgeMetaSchema });
1155
1448
  if (!parsed.success) return void 0;
1156
1449
  if (parsed.value.type !== bridgeType) return void 0;
1157
1450
  if (parsed.value.state !== "waiting") return void 0;
@@ -1413,6 +1706,7 @@ export {
1413
1706
  generateSandboxCredentialPlaceholder,
1414
1707
  getAiGatewayAuthFromEnv,
1415
1708
  getRestrictedSandboxSession,
1709
+ isHarnessAuthenticationEnvironment,
1416
1710
  isSandboxCredentialPlaceholder,
1417
1711
  logBridgeError,
1418
1712
  markBridgeStarting,