@ai-sdk/harness 1.0.92 → 1.0.94

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.
@@ -560,7 +560,14 @@ function shellQuote(value) {
560
560
  }
561
561
 
562
562
  // src/utils/write-skills.ts
563
+ import { createHash } from "crypto";
563
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._-]+$/;
564
571
  async function writeSkills({
565
572
  sandbox,
566
573
  rootDir,
@@ -572,60 +579,333 @@ async function writeSkills({
572
579
  invalidSkillFilePathMessage = ({ skillName, filePath }) => `Invalid skill file path for ${skillName}: ${filePath}`,
573
580
  trailingNewline = false
574
581
  }) {
575
- var _a, _b;
576
- for (const skill of skills) {
577
- validateSkillName({
578
- name: skill.name,
579
- pattern: skillNamePattern,
580
- 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
610
+ });
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
581
660
  });
582
- for (const file of (_a = skill.files) != null ? _a : []) {
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(
583
743
  normalizeSkillFilePath({
584
744
  skillName: skill.name,
585
745
  filePath: file.path,
586
746
  mode: filePathMode,
587
747
  message: invalidSkillFilePathMessage
588
- });
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;
589
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);
590
801
  }
591
- await sandbox.run({
592
- 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
+ `,
815
+ abortSignal
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)}`,
593
833
  abortSignal
594
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
+ }) {
595
862
  for (const skill of skills) {
596
- const name = validateSkillName({
597
- name: skill.name,
598
- pattern: skillNamePattern,
599
- message: invalidSkillNameMessage
600
- });
601
- const skillDir = path2.posix.join(rootDir, name);
602
- await sandbox.writeTextFile({
603
- path: path2.posix.join(skillDir, "SKILL.md"),
604
- content: renderSkillFile({ skill, trailingNewline }),
605
- abortSignal
606
- });
607
- for (const file of (_b = skill.files) != null ? _b : []) {
608
- const filePath = normalizeSkillFilePath({
609
- skillName: skill.name,
610
- filePath: file.path,
611
- mode: filePathMode,
612
- message: invalidSkillFilePathMessage
613
- });
863
+ const skillDir = path2.posix.join(rootDir, skill.name);
864
+ for (const file of skill.files) {
614
865
  await sandbox.writeTextFile({
615
- path: path2.posix.join(skillDir, filePath),
866
+ path: path2.posix.join(skillDir, file.path),
616
867
  content: file.content,
617
868
  abortSignal
618
869
  });
619
870
  }
620
871
  }
621
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
+ }
622
899
  function validateSkillName({
623
900
  name,
624
901
  pattern,
625
902
  message
626
903
  }) {
627
904
  var _a;
628
- 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 === "..") {
629
909
  throw new Error((_a = message == null ? void 0 : message({ name })) != null ? _a : `Invalid skill name: ${name}`);
630
910
  }
631
911
  return name;
@@ -662,7 +942,7 @@ ${skill.content}`;
662
942
 
663
943
  // src/utils/bridge-ready.ts
664
944
  import {
665
- safeParseJSON as safeParseJSON3
945
+ safeParseJSON as safeParseJSON4
666
946
  } from "@ai-sdk/provider-utils";
667
947
  import { z as z4 } from "zod/v4";
668
948
 
@@ -784,6 +1064,26 @@ var harnessV1ReasoningEndPartSchema = z2.object({
784
1064
  id: z2.string(),
785
1065
  harnessMetadata: harnessV1MetadataSchema.optional()
786
1066
  });
1067
+ var harnessV1ToolInputStartPartSchema = z2.object({
1068
+ type: z2.literal("tool-input-start"),
1069
+ id: z2.string(),
1070
+ toolName: z2.string(),
1071
+ providerMetadata: harnessV1ProviderMetadataSchema.optional(),
1072
+ providerExecuted: z2.boolean().optional(),
1073
+ dynamic: z2.boolean().optional(),
1074
+ title: z2.string().optional()
1075
+ });
1076
+ var harnessV1ToolInputDeltaPartSchema = z2.object({
1077
+ type: z2.literal("tool-input-delta"),
1078
+ id: z2.string(),
1079
+ delta: z2.string(),
1080
+ providerMetadata: harnessV1ProviderMetadataSchema.optional()
1081
+ });
1082
+ var harnessV1ToolInputEndPartSchema = z2.object({
1083
+ type: z2.literal("tool-input-end"),
1084
+ id: z2.string(),
1085
+ providerMetadata: harnessV1ProviderMetadataSchema.optional()
1086
+ });
787
1087
  var harnessV1ToolCallPartSchema = z2.object({
788
1088
  type: z2.literal("tool-call"),
789
1089
  toolCallId: z2.string(),
@@ -853,6 +1153,9 @@ var harnessV1StreamPartSchema = z2.discriminatedUnion("type", [
853
1153
  harnessV1ReasoningStartPartSchema,
854
1154
  harnessV1ReasoningDeltaPartSchema,
855
1155
  harnessV1ReasoningEndPartSchema,
1156
+ harnessV1ToolInputStartPartSchema,
1157
+ harnessV1ToolInputDeltaPartSchema,
1158
+ harnessV1ToolInputEndPartSchema,
856
1159
  harnessV1ToolCallPartSchema,
857
1160
  harnessV1ToolApprovalRequestPartSchema,
858
1161
  harnessV1ToolResultPartSchema,
@@ -957,6 +1260,9 @@ var harnessV1BridgeOutboundMessageSchema = z3.discriminatedUnion(
957
1260
  harnessV1ReasoningStartPartSchema,
958
1261
  harnessV1ReasoningDeltaPartSchema,
959
1262
  harnessV1ReasoningEndPartSchema,
1263
+ harnessV1ToolInputStartPartSchema,
1264
+ harnessV1ToolInputDeltaPartSchema,
1265
+ harnessV1ToolInputEndPartSchema,
960
1266
  harnessV1ToolCallPartSchema,
961
1267
  harnessV1ToolApprovalRequestPartSchema,
962
1268
  harnessV1ToolResultPartSchema,
@@ -1127,7 +1433,7 @@ async function waitForBridgeReady({
1127
1433
  if (value === void 0) continue;
1128
1434
  for (const line of decoder.push(value)) {
1129
1435
  pushTail({ lines: stdoutTail, line });
1130
- const parsed = await safeParseJSON3({
1436
+ const parsed = await safeParseJSON4({
1131
1437
  text: line,
1132
1438
  schema: harnessV1BridgeReadySchema
1133
1439
  });
@@ -1164,7 +1470,7 @@ async function readBridgeMetaReady({
1164
1470
  })
1165
1471
  ).catch(() => null);
1166
1472
  if (raw == null) return void 0;
1167
- const parsed = await safeParseJSON3({ text: raw, schema: bridgeMetaSchema });
1473
+ const parsed = await safeParseJSON4({ text: raw, schema: bridgeMetaSchema });
1168
1474
  if (!parsed.success) return void 0;
1169
1475
  if (parsed.value.type !== bridgeType) return void 0;
1170
1476
  if (parsed.value.state !== "waiting") return void 0;