@builder.io/ai-utils 0.111.1 → 0.112.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder.io/ai-utils",
3
- "version": "0.111.1",
3
+ "version": "0.112.1",
4
4
  "description": "Builder.io AI utils",
5
5
  "files": [
6
6
  "src"
package/src/codegen.d.ts CHANGED
@@ -528,10 +528,10 @@ export declare const PublishEvidenceToolInputSchema: z.ZodObject<{
528
528
  "not-reproduced": "not-reproduced";
529
529
  }>;
530
530
  summary: z.ZodString;
531
- screenshots: z.ZodArray<z.ZodObject<{
531
+ screenshots: z.ZodOptional<z.ZodArray<z.ZodObject<{
532
532
  path: z.ZodString;
533
533
  caption: z.ZodString;
534
- }, z.core.$strip>>;
534
+ }, z.core.$strip>>>;
535
535
  recordings: z.ZodOptional<z.ZodArray<z.ZodObject<{
536
536
  recording_id: z.ZodString;
537
537
  }, z.core.$strip>>>;
@@ -680,7 +680,7 @@ export interface TimelineSubmissionMetadata {
680
680
  * Configuration values proposed by the setup analyzer agent
681
681
  */
682
682
  export declare const SetupAnalysisValuesSchema: z.ZodObject<{
683
- projectOverview: z.ZodObject<{
683
+ projectOverview: z.ZodNullable<z.ZodObject<{
684
684
  framework: z.ZodNullable<z.ZodString>;
685
685
  packageManager: z.ZodNullable<z.ZodEnum<{
686
686
  bun: "bun";
@@ -693,7 +693,7 @@ export declare const SetupAnalysisValuesSchema: z.ZodObject<{
693
693
  javascript: "javascript";
694
694
  typescript: "typescript";
695
695
  }>>;
696
- }, z.core.$strip>;
696
+ }, z.core.$strip>>;
697
697
  rootDirectory: z.ZodNullable<z.ZodObject<{
698
698
  path: z.ZodString;
699
699
  reason: z.ZodString;
@@ -732,9 +732,86 @@ export declare const SetupAnalysisValuesSchema: z.ZodObject<{
732
732
  }, z.core.$strip>>;
733
733
  }, z.core.$strip>;
734
734
  export type SetupAnalysisValues = z.infer<typeof SetupAnalysisValuesSchema>;
735
+ /**
736
+ * What a ProposeConfig call may carry: any subset of the setup values. A
737
+ * nullable field set to null, or any field omitted, means "no suggestion";
738
+ * the array fields (runtimeDependencies, environmentVariables) aren't
739
+ * nullable, so `[]` or omitting them means the same. Either way the existing
740
+ * value is left unchanged when the user applies the proposal.
741
+ */
742
+ export declare const ProposedSetupValuesSchema: z.ZodObject<{
743
+ projectOverview: z.ZodOptional<z.ZodNullable<z.ZodObject<{
744
+ framework: z.ZodNullable<z.ZodString>;
745
+ packageManager: z.ZodNullable<z.ZodEnum<{
746
+ bun: "bun";
747
+ npm: "npm";
748
+ pnpm: "pnpm";
749
+ yarn: "yarn";
750
+ }>>;
751
+ isMonorepo: z.ZodBoolean;
752
+ detectedLanguage: z.ZodNullable<z.ZodEnum<{
753
+ javascript: "javascript";
754
+ typescript: "typescript";
755
+ }>>;
756
+ }, z.core.$strip>>>;
757
+ rootDirectory: z.ZodOptional<z.ZodNullable<z.ZodObject<{
758
+ path: z.ZodString;
759
+ reason: z.ZodString;
760
+ }, z.core.$strip>>>;
761
+ installCommand: z.ZodOptional<z.ZodNullable<z.ZodObject<{
762
+ command: z.ZodString;
763
+ reason: z.ZodString;
764
+ }, z.core.$strip>>>;
765
+ runtimeDependencies: z.ZodOptional<z.ZodArray<z.ZodObject<{
766
+ tool: z.ZodString;
767
+ version: z.ZodString;
768
+ source: z.ZodString;
769
+ }, z.core.$strip>>>;
770
+ devServer: z.ZodOptional<z.ZodNullable<z.ZodObject<{
771
+ command: z.ZodString;
772
+ url: z.ZodString;
773
+ port: z.ZodNumber;
774
+ reason: z.ZodString;
775
+ }, z.core.$strip>>>;
776
+ environmentVariables: z.ZodOptional<z.ZodArray<z.ZodObject<{
777
+ key: z.ZodString;
778
+ description: z.ZodString;
779
+ isRequired: z.ZodBoolean;
780
+ isSecret: z.ZodBoolean;
781
+ defaultValue: z.ZodNullable<z.ZodString>;
782
+ source: z.ZodString;
783
+ }, z.core.$strip>>>;
784
+ validationScript: z.ZodOptional<z.ZodNullable<z.ZodObject<{
785
+ command: z.ZodString;
786
+ reason: z.ZodString;
787
+ }, z.core.$strip>>>;
788
+ npmrcContents: z.ZodOptional<z.ZodNullable<z.ZodString>>;
789
+ hasHotModuleReload: z.ZodOptional<z.ZodNullable<z.ZodObject<{
790
+ value: z.ZodBoolean;
791
+ reason: z.ZodString;
792
+ }, z.core.$strip>>>;
793
+ }, z.core.$strip>;
794
+ export type ProposedSetupValues = z.infer<typeof ProposedSetupValuesSchema>;
795
+ /**
796
+ * True only when a proposal affirmatively says nothing needs installing: an
797
+ * explicitly empty install command (`{ command: "" }`) and an explicit empty
798
+ * `runtimeDependencies` list. A null or omitted install command means "no
799
+ * suggestion", so it is unknown rather than "no dependencies", even when the
800
+ * proposal also carries projectOverview.
801
+ */
802
+ export declare function proposalNeedsNoDependencies(config: ProposedSetupValues | null | undefined): boolean;
803
+ /**
804
+ * `noDependencies` for replaying a stored propose_config step. Uses the
805
+ * decision recorded on the action (`toolMeta.noDependencies`) when present,
806
+ * so a replay always matches the live run. Actions recorded before that
807
+ * marker existed keep the original replay reading (no install command and no
808
+ * runtime dependencies means nothing to install), so old sessions don't
809
+ * change on reload.
810
+ */
811
+ export declare function replayedProposalNeedsNoDependencies(config: ProposedSetupValues | null | undefined, recorded: ToolActionMeta | undefined): boolean;
735
812
  export declare const ProposeConfigToolInputSchema: z.ZodObject<{
736
813
  config: z.ZodObject<{
737
- projectOverview: z.ZodObject<{
814
+ projectOverview: z.ZodOptional<z.ZodNullable<z.ZodObject<{
738
815
  framework: z.ZodNullable<z.ZodString>;
739
816
  packageManager: z.ZodNullable<z.ZodEnum<{
740
817
  bun: "bun";
@@ -747,43 +824,43 @@ export declare const ProposeConfigToolInputSchema: z.ZodObject<{
747
824
  javascript: "javascript";
748
825
  typescript: "typescript";
749
826
  }>>;
750
- }, z.core.$strip>;
751
- rootDirectory: z.ZodNullable<z.ZodObject<{
827
+ }, z.core.$strip>>>;
828
+ rootDirectory: z.ZodOptional<z.ZodNullable<z.ZodObject<{
752
829
  path: z.ZodString;
753
830
  reason: z.ZodString;
754
- }, z.core.$strip>>;
755
- installCommand: z.ZodNullable<z.ZodObject<{
831
+ }, z.core.$strip>>>;
832
+ installCommand: z.ZodOptional<z.ZodNullable<z.ZodObject<{
756
833
  command: z.ZodString;
757
834
  reason: z.ZodString;
758
- }, z.core.$strip>>;
759
- runtimeDependencies: z.ZodArray<z.ZodObject<{
835
+ }, z.core.$strip>>>;
836
+ runtimeDependencies: z.ZodOptional<z.ZodArray<z.ZodObject<{
760
837
  tool: z.ZodString;
761
838
  version: z.ZodString;
762
839
  source: z.ZodString;
763
- }, z.core.$strip>>;
764
- devServer: z.ZodNullable<z.ZodObject<{
840
+ }, z.core.$strip>>>;
841
+ devServer: z.ZodOptional<z.ZodNullable<z.ZodObject<{
765
842
  command: z.ZodString;
766
843
  url: z.ZodString;
767
844
  port: z.ZodNumber;
768
845
  reason: z.ZodString;
769
- }, z.core.$strip>>;
770
- environmentVariables: z.ZodArray<z.ZodObject<{
846
+ }, z.core.$strip>>>;
847
+ environmentVariables: z.ZodOptional<z.ZodArray<z.ZodObject<{
771
848
  key: z.ZodString;
772
849
  description: z.ZodString;
773
850
  isRequired: z.ZodBoolean;
774
851
  isSecret: z.ZodBoolean;
775
852
  defaultValue: z.ZodNullable<z.ZodString>;
776
853
  source: z.ZodString;
777
- }, z.core.$strip>>;
778
- validationScript: z.ZodNullable<z.ZodObject<{
854
+ }, z.core.$strip>>>;
855
+ validationScript: z.ZodOptional<z.ZodNullable<z.ZodObject<{
779
856
  command: z.ZodString;
780
857
  reason: z.ZodString;
781
- }, z.core.$strip>>;
782
- npmrcContents: z.ZodNullable<z.ZodString>;
783
- hasHotModuleReload: z.ZodNullable<z.ZodObject<{
858
+ }, z.core.$strip>>>;
859
+ npmrcContents: z.ZodOptional<z.ZodNullable<z.ZodString>>;
860
+ hasHotModuleReload: z.ZodOptional<z.ZodNullable<z.ZodObject<{
784
861
  value: z.ZodBoolean;
785
862
  reason: z.ZodString;
786
- }, z.core.$strip>>;
863
+ }, z.core.$strip>>>;
787
864
  }, z.core.$strip>;
788
865
  message: z.ZodOptional<z.ZodString>;
789
866
  }, z.core.$strip>;
@@ -1706,10 +1783,10 @@ export declare const CodeGenToolMapSchema: z.ZodObject<{
1706
1783
  "not-reproduced": "not-reproduced";
1707
1784
  }>;
1708
1785
  summary: z.ZodString;
1709
- screenshots: z.ZodArray<z.ZodObject<{
1786
+ screenshots: z.ZodOptional<z.ZodArray<z.ZodObject<{
1710
1787
  path: z.ZodString;
1711
1788
  caption: z.ZodString;
1712
- }, z.core.$strip>>;
1789
+ }, z.core.$strip>>>;
1713
1790
  recordings: z.ZodOptional<z.ZodArray<z.ZodObject<{
1714
1791
  recording_id: z.ZodString;
1715
1792
  }, z.core.$strip>>>;
@@ -1762,7 +1839,7 @@ export declare const CodeGenToolMapSchema: z.ZodObject<{
1762
1839
  }, z.core.$strip>;
1763
1840
  ProposeConfig: z.ZodObject<{
1764
1841
  config: z.ZodObject<{
1765
- projectOverview: z.ZodObject<{
1842
+ projectOverview: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1766
1843
  framework: z.ZodNullable<z.ZodString>;
1767
1844
  packageManager: z.ZodNullable<z.ZodEnum<{
1768
1845
  bun: "bun";
@@ -1775,43 +1852,43 @@ export declare const CodeGenToolMapSchema: z.ZodObject<{
1775
1852
  javascript: "javascript";
1776
1853
  typescript: "typescript";
1777
1854
  }>>;
1778
- }, z.core.$strip>;
1779
- rootDirectory: z.ZodNullable<z.ZodObject<{
1855
+ }, z.core.$strip>>>;
1856
+ rootDirectory: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1780
1857
  path: z.ZodString;
1781
1858
  reason: z.ZodString;
1782
- }, z.core.$strip>>;
1783
- installCommand: z.ZodNullable<z.ZodObject<{
1859
+ }, z.core.$strip>>>;
1860
+ installCommand: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1784
1861
  command: z.ZodString;
1785
1862
  reason: z.ZodString;
1786
- }, z.core.$strip>>;
1787
- runtimeDependencies: z.ZodArray<z.ZodObject<{
1863
+ }, z.core.$strip>>>;
1864
+ runtimeDependencies: z.ZodOptional<z.ZodArray<z.ZodObject<{
1788
1865
  tool: z.ZodString;
1789
1866
  version: z.ZodString;
1790
1867
  source: z.ZodString;
1791
- }, z.core.$strip>>;
1792
- devServer: z.ZodNullable<z.ZodObject<{
1868
+ }, z.core.$strip>>>;
1869
+ devServer: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1793
1870
  command: z.ZodString;
1794
1871
  url: z.ZodString;
1795
1872
  port: z.ZodNumber;
1796
1873
  reason: z.ZodString;
1797
- }, z.core.$strip>>;
1798
- environmentVariables: z.ZodArray<z.ZodObject<{
1874
+ }, z.core.$strip>>>;
1875
+ environmentVariables: z.ZodOptional<z.ZodArray<z.ZodObject<{
1799
1876
  key: z.ZodString;
1800
1877
  description: z.ZodString;
1801
1878
  isRequired: z.ZodBoolean;
1802
1879
  isSecret: z.ZodBoolean;
1803
1880
  defaultValue: z.ZodNullable<z.ZodString>;
1804
1881
  source: z.ZodString;
1805
- }, z.core.$strip>>;
1806
- validationScript: z.ZodNullable<z.ZodObject<{
1882
+ }, z.core.$strip>>>;
1883
+ validationScript: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1807
1884
  command: z.ZodString;
1808
1885
  reason: z.ZodString;
1809
- }, z.core.$strip>>;
1810
- npmrcContents: z.ZodNullable<z.ZodString>;
1811
- hasHotModuleReload: z.ZodNullable<z.ZodObject<{
1886
+ }, z.core.$strip>>>;
1887
+ npmrcContents: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1888
+ hasHotModuleReload: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1812
1889
  value: z.ZodBoolean;
1813
1890
  reason: z.ZodString;
1814
- }, z.core.$strip>>;
1891
+ }, z.core.$strip>>>;
1815
1892
  }, z.core.$strip>;
1816
1893
  message: z.ZodOptional<z.ZodString>;
1817
1894
  }, z.core.$strip>;
@@ -2406,6 +2483,15 @@ export declare const PublishAgentMessageRequestSchema: z.ZodObject<{
2406
2483
  }, z.core.$strip>;
2407
2484
  }, z.core.$strip>], "type">>]>;
2408
2485
  is_error: z.ZodOptional<z.ZodBoolean>;
2486
+ error_kind: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
2487
+ command_not_found: "command_not_found";
2488
+ guardrail: "guardrail";
2489
+ invalid_input: "invalid_input";
2490
+ non_zero_exit: "non_zero_exit";
2491
+ policy_block: "policy_block";
2492
+ tool_failure: "tool_failure";
2493
+ user_abort: "user_abort";
2494
+ }>>>;
2409
2495
  cache: z.ZodOptional<z.ZodBoolean>;
2410
2496
  ephemeral: z.ZodOptional<z.ZodBoolean>;
2411
2497
  structured_result: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
@@ -2817,6 +2903,15 @@ export declare const CodeGenInputOptionsSchema: z.ZodObject<{
2817
2903
  }, z.core.$strip>;
2818
2904
  }, z.core.$strip>], "type">>]>;
2819
2905
  is_error: z.ZodOptional<z.ZodBoolean>;
2906
+ error_kind: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
2907
+ command_not_found: "command_not_found";
2908
+ guardrail: "guardrail";
2909
+ invalid_input: "invalid_input";
2910
+ non_zero_exit: "non_zero_exit";
2911
+ policy_block: "policy_block";
2912
+ tool_failure: "tool_failure";
2913
+ user_abort: "user_abort";
2914
+ }>>>;
2820
2915
  cache: z.ZodOptional<z.ZodBoolean>;
2821
2916
  ephemeral: z.ZodOptional<z.ZodBoolean>;
2822
2917
  structured_result: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
@@ -3185,6 +3280,19 @@ export interface ActionItem {
3185
3280
  incomplete?: boolean;
3186
3281
  suggestions?: PromptSuggestion[];
3187
3282
  errors?: string[];
3283
+ /**
3284
+ * Decisions the service recorded for a tool call, persisted with the
3285
+ * action so replays reproduce what the live run showed. Optional and
3286
+ * ignored by clients that don't know it.
3287
+ */
3288
+ toolMeta?: ToolActionMeta;
3289
+ }
3290
+ export interface ToolActionMeta {
3291
+ /**
3292
+ * ProposeConfig: `proposalNeedsNoDependencies` for this call's config.
3293
+ * Absent on actions recorded before it existed.
3294
+ */
3295
+ noDependencies?: boolean;
3188
3296
  }
3189
3297
  export interface GenerateCompletionStepThinking {
3190
3298
  type: "thinking";
@@ -3647,7 +3755,8 @@ export interface GenerateCompletionStepIde {
3647
3755
  }
3648
3756
  export interface GenerateCompletionStepProposeConfig {
3649
3757
  type: "propose_config";
3650
- config: SetupAnalysisValues;
3758
+ /** May be partial: omitted or null fields mean "no suggestion". */
3759
+ config: ProposedSetupValues;
3651
3760
  message?: string;
3652
3761
  /** When true, the project requires no dependency installation. The UI should not show an Apply dialog for dependencies. */
3653
3762
  noDependencies?: boolean;
package/src/codegen.js CHANGED
@@ -277,7 +277,7 @@ export const PowerShellToolInputSchema = z
277
277
  description: "The PowerShell command to run, for example 'npm install' or 'yarn test'",
278
278
  }),
279
279
  timeout: z.number().nullish().meta({
280
- description: "The timeout in milliseconds for the command to run. Choose an appropriate timeout based on the command",
280
+ description: "Optional timeout in milliseconds (max/default 600000). Omit it for installs, builds, type checks and test runs, which are often slow on Windows.",
281
281
  }),
282
282
  description: z.string().nullish().meta({
283
283
  description: "A human readable description of the command to run, for example 'install dependencies' or 'run tests'. This description will be used to explain the command to the user.",
@@ -789,8 +789,9 @@ export const PublishEvidenceToolInputSchema = z
789
789
  }),
790
790
  caption: z.string(),
791
791
  }))
792
+ .optional()
792
793
  .meta({
793
- description: "Stills that support the fix; the PR shows them under the recording.",
794
+ description: "Stills that support the fix; the PR shows them under the recording. Expected for a 'fixed' issue; omit it or send [] when there are none.",
794
795
  }),
795
796
  recordings: z
796
797
  .array(z.object({
@@ -847,12 +848,17 @@ export const ReportTestOutcomeToolInputSchema = z
847
848
  */
848
849
  export const SetupAnalysisValuesSchema = z
849
850
  .object({
850
- projectOverview: z.object({
851
+ // Nullable: in a partial proposal (e.g. only runtimeDependencies) a null
852
+ // field, or an empty array field, means "no suggestion" and leaves the
853
+ // existing value unchanged when applied.
854
+ projectOverview: z
855
+ .object({
851
856
  framework: z.string().nullable(),
852
857
  packageManager: z.enum(["npm", "yarn", "pnpm", "bun"]).nullable(),
853
858
  isMonorepo: z.boolean(),
854
859
  detectedLanguage: z.enum(["typescript", "javascript"]).nullable(),
855
- }),
860
+ })
861
+ .nullable(),
856
862
  rootDirectory: z
857
863
  .object({
858
864
  path: z.string(),
@@ -901,9 +907,52 @@ export const SetupAnalysisValuesSchema = z
901
907
  .nullable(),
902
908
  })
903
909
  .meta({ title: "SetupAnalysisValues" });
910
+ /**
911
+ * What a ProposeConfig call may carry: any subset of the setup values. A
912
+ * nullable field set to null, or any field omitted, means "no suggestion";
913
+ * the array fields (runtimeDependencies, environmentVariables) aren't
914
+ * nullable, so `[]` or omitting them means the same. Either way the existing
915
+ * value is left unchanged when the user applies the proposal.
916
+ */
917
+ export const ProposedSetupValuesSchema = SetupAnalysisValuesSchema.partial().meta({ title: "ProposedSetupValues" });
918
+ /**
919
+ * True only when a proposal affirmatively says nothing needs installing: an
920
+ * explicitly empty install command (`{ command: "" }`) and an explicit empty
921
+ * `runtimeDependencies` list. A null or omitted install command means "no
922
+ * suggestion", so it is unknown rather than "no dependencies", even when the
923
+ * proposal also carries projectOverview.
924
+ */
925
+ export function proposalNeedsNoDependencies(config) {
926
+ var _a;
927
+ if (!config || !Array.isArray(config.runtimeDependencies))
928
+ return false;
929
+ if (config.runtimeDependencies.length > 0)
930
+ return false;
931
+ const installCommand = config.installCommand;
932
+ return (installCommand != null &&
933
+ typeof installCommand === "object" &&
934
+ !((_a = installCommand.command) === null || _a === void 0 ? void 0 : _a.trim()));
935
+ }
936
+ /**
937
+ * `noDependencies` for replaying a stored propose_config step. Uses the
938
+ * decision recorded on the action (`toolMeta.noDependencies`) when present,
939
+ * so a replay always matches the live run. Actions recorded before that
940
+ * marker existed keep the original replay reading (no install command and no
941
+ * runtime dependencies means nothing to install), so old sessions don't
942
+ * change on reload.
943
+ */
944
+ export function replayedProposalNeedsNoDependencies(config, recorded) {
945
+ var _a;
946
+ if (typeof (recorded === null || recorded === void 0 ? void 0 : recorded.noDependencies) === "boolean") {
947
+ return recorded.noDependencies;
948
+ }
949
+ return (config != null &&
950
+ !((_a = config.installCommand) === null || _a === void 0 ? void 0 : _a.command) &&
951
+ (!config.runtimeDependencies || config.runtimeDependencies.length === 0));
952
+ }
904
953
  export const ProposeConfigToolInputSchema = z
905
954
  .object({
906
- config: SetupAnalysisValuesSchema,
955
+ config: ProposedSetupValuesSchema,
907
956
  message: z.string().optional(),
908
957
  })
909
958
  .meta({ title: "ProposeConfigToolInput" });
package/src/index.d.ts CHANGED
@@ -26,4 +26,5 @@ export * from "./credits.js";
26
26
  export * from "./embeddings.js";
27
27
  export * from "./system-one.js";
28
28
  export * from "./mcp-servers.js";
29
+ export * from "./tool-failure.js";
29
30
  export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
package/src/index.js CHANGED
@@ -26,4 +26,5 @@ export * from "./credits.js";
26
26
  export * from "./embeddings.js";
27
27
  export * from "./system-one.js";
28
28
  export * from "./mcp-servers.js";
29
+ export * from "./tool-failure.js";
29
30
  export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
package/src/messages.d.ts CHANGED
@@ -570,6 +570,15 @@ export declare const ContentMessageItemToolResultSchema: z.ZodObject<{
570
570
  }, z.core.$strip>;
571
571
  }, z.core.$strip>], "type">>]>;
572
572
  is_error: z.ZodOptional<z.ZodBoolean>;
573
+ error_kind: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
574
+ command_not_found: "command_not_found";
575
+ guardrail: "guardrail";
576
+ invalid_input: "invalid_input";
577
+ non_zero_exit: "non_zero_exit";
578
+ policy_block: "policy_block";
579
+ tool_failure: "tool_failure";
580
+ user_abort: "user_abort";
581
+ }>>>;
573
582
  cache: z.ZodOptional<z.ZodBoolean>;
574
583
  ephemeral: z.ZodOptional<z.ZodBoolean>;
575
584
  structured_result: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
package/src/messages.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { ToolFailureKindSchema } from "./tool-failure.js";
2
3
  export function getContentText(message) {
3
4
  if (typeof message === "string") {
4
5
  return message;
@@ -167,6 +168,11 @@ export const ContentMessageItemToolResultSchema = z
167
168
  ])),
168
169
  ]),
169
170
  is_error: z.boolean().optional(),
171
+ // An unrecognized kind (e.g. from a newer client) parses as absent so a
172
+ // strict parse never rejects the request over it.
173
+ error_kind: ToolFailureKindSchema.optional().catch(undefined).meta({
174
+ description: "Why the call failed, set by the code that failed. Only meaningful with `is_error: true`; when absent the service infers the reason from the message.",
175
+ }),
170
176
  cache: z.boolean().optional(),
171
177
  ephemeral: z.boolean().optional(),
172
178
  structured_result: z.record(z.string(), z.any()).optional(),
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { proposalNeedsNoDependencies, replayedProposalNeedsNoDependencies, } from "./codegen";
3
+ const overview = {
4
+ framework: "vite",
5
+ packageManager: "npm",
6
+ isMonorepo: false,
7
+ detectedLanguage: "typescript",
8
+ };
9
+ describe("proposalNeedsNoDependencies", () => {
10
+ it("is unknown (false) when the install command is null, even with projectOverview", () => {
11
+ // null means "no suggestion", and partial proposals may carry
12
+ // projectOverview forward, so this is not an affirmative signal.
13
+ expect(proposalNeedsNoDependencies({
14
+ projectOverview: overview,
15
+ installCommand: null,
16
+ runtimeDependencies: [],
17
+ })).toBe(false);
18
+ });
19
+ it("is true for an explicitly empty install command and no runtime deps", () => {
20
+ expect(proposalNeedsNoDependencies({
21
+ installCommand: { command: "", reason: "nothing to install" },
22
+ runtimeDependencies: [],
23
+ })).toBe(true);
24
+ });
25
+ it("is unknown (false) for a partial env-vars-only proposal", () => {
26
+ expect(proposalNeedsNoDependencies({
27
+ projectOverview: null,
28
+ installCommand: null,
29
+ runtimeDependencies: [],
30
+ environmentVariables: [
31
+ {
32
+ key: "API_URL",
33
+ description: "",
34
+ isRequired: true,
35
+ isSecret: false,
36
+ defaultValue: null,
37
+ source: ".env.example",
38
+ },
39
+ ],
40
+ })).toBe(false);
41
+ expect(proposalNeedsNoDependencies({ environmentVariables: [] })).toBe(false);
42
+ });
43
+ it("is false when something needs installing or config is missing", () => {
44
+ expect(proposalNeedsNoDependencies({
45
+ projectOverview: overview,
46
+ installCommand: { command: "npm ci", reason: "lockfile" },
47
+ runtimeDependencies: [],
48
+ })).toBe(false);
49
+ expect(proposalNeedsNoDependencies({
50
+ projectOverview: overview,
51
+ installCommand: null,
52
+ runtimeDependencies: [{ tool: "node", version: "22", source: "" }],
53
+ })).toBe(false);
54
+ expect(proposalNeedsNoDependencies(undefined)).toBe(false);
55
+ });
56
+ });
57
+ describe("replayedProposalNeedsNoDependencies", () => {
58
+ const fullShapeProposal = {
59
+ projectOverview: overview,
60
+ rootDirectory: null,
61
+ installCommand: null,
62
+ runtimeDependencies: [],
63
+ devServer: null,
64
+ environmentVariables: [],
65
+ validationScript: null,
66
+ npmrcContents: null,
67
+ hasHotModuleReload: null,
68
+ };
69
+ it("uses the recorded decision, so a new full-shape proposal replays like live", () => {
70
+ const live = proposalNeedsNoDependencies(fullShapeProposal);
71
+ expect(live).toBe(false);
72
+ expect(replayedProposalNeedsNoDependencies(fullShapeProposal, {
73
+ noDependencies: live,
74
+ })).toBe(false);
75
+ });
76
+ it("uses a recorded decision for a partial proposal too", () => {
77
+ const partial = { environmentVariables: [], runtimeDependencies: [] };
78
+ expect(replayedProposalNeedsNoDependencies(partial, {
79
+ noDependencies: proposalNeedsNoDependencies(partial),
80
+ })).toBe(false);
81
+ expect(replayedProposalNeedsNoDependencies({
82
+ installCommand: { command: "", reason: "static" },
83
+ runtimeDependencies: [],
84
+ }, { noDependencies: true })).toBe(true);
85
+ });
86
+ it("keeps the original replay reading for legacy actions without a marker", () => {
87
+ expect(replayedProposalNeedsNoDependencies(fullShapeProposal, undefined)).toBe(true);
88
+ expect(replayedProposalNeedsNoDependencies({
89
+ ...fullShapeProposal,
90
+ installCommand: { command: "npm ci", reason: "lockfile" },
91
+ }, undefined)).toBe(false);
92
+ expect(replayedProposalNeedsNoDependencies(undefined, undefined)).toBe(false);
93
+ });
94
+ });
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Why a tool result came out the way it did. `is_error` alone conflates a
4
+ * broken tool with a tool that worked and reported a negative outcome (no
5
+ * matches, failing tests), so only `tool_failure` means the tool itself broke.
6
+ * Closed set: these are the values of the `reason` label on the service's
7
+ * tool-result metric, so adding one adds metric series.
8
+ */
9
+ export declare const ToolResultReasonSchema: z.ZodEnum<{
10
+ command_not_found: "command_not_found";
11
+ empty_result: "empty_result";
12
+ guardrail: "guardrail";
13
+ invalid_input: "invalid_input";
14
+ non_zero_exit: "non_zero_exit";
15
+ policy_block: "policy_block";
16
+ success: "success";
17
+ tool_failure: "tool_failure";
18
+ user_abort: "user_abort";
19
+ }>;
20
+ export type ToolResultReason = z.infer<typeof ToolResultReasonSchema>;
21
+ /** A reason only a failed tool call can have. */
22
+ export declare const ToolFailureKindSchema: z.ZodEnum<{
23
+ command_not_found: "command_not_found";
24
+ guardrail: "guardrail";
25
+ invalid_input: "invalid_input";
26
+ non_zero_exit: "non_zero_exit";
27
+ policy_block: "policy_block";
28
+ tool_failure: "tool_failure";
29
+ user_abort: "user_abort";
30
+ }>;
31
+ export type ToolFailureKind = z.infer<typeof ToolFailureKindSchema>;
32
+ export declare function isToolFailureKind(value: unknown): value is ToolFailureKind;
33
+ export interface ToolFailure {
34
+ kind: ToolFailureKind;
35
+ /** Tool-specific detail for logs and tests. Never a metric label. */
36
+ code?: string;
37
+ /** Whether repeating the same call unchanged can succeed. */
38
+ retryable: boolean;
39
+ }
40
+ export interface Failure extends ToolFailure {
41
+ /** Shown to the model or the user; never empty. */
42
+ message: string;
43
+ }
44
+ /**
45
+ * Return type for fallible helpers, in place of `T | undefined`, so a caller
46
+ * can't drop the reason a call failed.
47
+ */
48
+ export type Result<T> = {
49
+ ok: true;
50
+ value: T;
51
+ } | {
52
+ ok: false;
53
+ error: Failure;
54
+ };
55
+ export declare function okResult<T>(value: T): {
56
+ ok: true;
57
+ value: T;
58
+ };
59
+ export declare function failResult(kind: ToolFailureKind, message: string, options: {
60
+ retryable: boolean;
61
+ code?: string;
62
+ }): {
63
+ ok: false;
64
+ error: Failure;
65
+ };
66
+ /**
67
+ * Throw from inside a tool to fail it with a kind. A tool runner turns it
68
+ * into a typed failure; any other thrown error stays untyped.
69
+ */
70
+ export declare class ToolError extends Error {
71
+ readonly failure: ToolFailure;
72
+ constructor(kind: ToolFailureKind, message: string, options: {
73
+ retryable: boolean;
74
+ code?: string;
75
+ cause?: unknown;
76
+ });
77
+ }
78
+ /** A failed tool resolution, in the shape both the CLI and the service runners accept. */
79
+ export interface ToolFailureReturn {
80
+ isError: true;
81
+ toolResult: string;
82
+ error: ToolFailure;
83
+ title?: string;
84
+ structuredResult?: Record<string, any>;
85
+ }
86
+ export declare function toolFail(kind: ToolFailureKind, message: string, options: {
87
+ retryable: boolean;
88
+ code?: string;
89
+ title?: string;
90
+ structuredResult?: Record<string, any>;
91
+ }): ToolFailureReturn;
92
+ /** Passes a fallible helper's failure through as the tool's own. */
93
+ export declare function toolFailFrom(failure: Failure, options?: {
94
+ title?: string;
95
+ }): ToolFailureReturn;
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Why a tool result came out the way it did. `is_error` alone conflates a
4
+ * broken tool with a tool that worked and reported a negative outcome (no
5
+ * matches, failing tests), so only `tool_failure` means the tool itself broke.
6
+ * Closed set: these are the values of the `reason` label on the service's
7
+ * tool-result metric, so adding one adds metric series.
8
+ */
9
+ export const ToolResultReasonSchema = z
10
+ .enum([
11
+ "success",
12
+ "empty_result",
13
+ "non_zero_exit",
14
+ "command_not_found",
15
+ "policy_block",
16
+ "guardrail",
17
+ "invalid_input",
18
+ "user_abort",
19
+ "tool_failure",
20
+ ])
21
+ .meta({ title: "ToolResultReason" });
22
+ /** A reason only a failed tool call can have. */
23
+ export const ToolFailureKindSchema = ToolResultReasonSchema.exclude([
24
+ "success",
25
+ "empty_result",
26
+ ]).meta({ title: "ToolFailureKind" });
27
+ export function isToolFailureKind(value) {
28
+ return ToolFailureKindSchema.safeParse(value).success;
29
+ }
30
+ export function okResult(value) {
31
+ return { ok: true, value };
32
+ }
33
+ export function failResult(kind, message, options) {
34
+ return { ok: false, error: { kind, message, ...options } };
35
+ }
36
+ /**
37
+ * Throw from inside a tool to fail it with a kind. A tool runner turns it
38
+ * into a typed failure; any other thrown error stays untyped.
39
+ */
40
+ export class ToolError extends Error {
41
+ constructor(kind, message, options) {
42
+ super(message, { cause: options.cause });
43
+ this.name = "ToolError";
44
+ this.failure = {
45
+ kind,
46
+ retryable: options.retryable,
47
+ ...(options.code ? { code: options.code } : {}),
48
+ };
49
+ }
50
+ }
51
+ export function toolFail(kind, message, options) {
52
+ const { retryable, code, title, structuredResult } = options;
53
+ return {
54
+ isError: true,
55
+ toolResult: message,
56
+ error: { kind, retryable, ...(code ? { code } : {}) },
57
+ ...(title !== undefined ? { title } : {}),
58
+ ...(structuredResult ? { structuredResult } : {}),
59
+ };
60
+ }
61
+ /** Passes a fallible helper's failure through as the tool's own. */
62
+ export function toolFailFrom(failure, options = {}) {
63
+ return toolFail(failure.kind, failure.message, {
64
+ retryable: failure.retryable,
65
+ code: failure.code,
66
+ title: options.title,
67
+ });
68
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ContentMessageItemToolResultSchema } from "./messages";
3
+ import { failResult, isToolFailureKind, okResult } from "./tool-failure";
4
+ const base = {
5
+ type: "tool_result",
6
+ tool_use_id: "toolu_1",
7
+ content: "Grep produced too much output.",
8
+ is_error: true,
9
+ };
10
+ describe("tool_result error_kind", () => {
11
+ it("keeps a valid kind", () => {
12
+ expect(ContentMessageItemToolResultSchema.parse({
13
+ ...base,
14
+ error_kind: "guardrail",
15
+ }).error_kind).toBe("guardrail");
16
+ });
17
+ it("parses an unknown kind as absent instead of rejecting", () => {
18
+ const parsed = ContentMessageItemToolResultSchema.parse({
19
+ ...base,
20
+ error_kind: "rate_limited",
21
+ });
22
+ expect(parsed.error_kind).toBeUndefined();
23
+ expect(parsed.content).toBe(base.content);
24
+ });
25
+ it("parses a result without a kind", () => {
26
+ expect(ContentMessageItemToolResultSchema.parse(base).error_kind).toBeUndefined();
27
+ });
28
+ it("strips other unknown keys, as older schemas strip error_kind", () => {
29
+ const parsed = ContentMessageItemToolResultSchema.parse({
30
+ ...base,
31
+ error_code: "x",
32
+ });
33
+ expect(parsed).not.toHaveProperty("error_code");
34
+ });
35
+ });
36
+ describe("isToolFailureKind", () => {
37
+ it.each(["guardrail", "tool_failure", "user_abort"])("accepts %s", (kind) => {
38
+ expect(isToolFailureKind(kind)).toBe(true);
39
+ });
40
+ it.each(["success", "empty_result", "rate_limited", 1, undefined])("rejects %s", (kind) => {
41
+ expect(isToolFailureKind(kind)).toBe(false);
42
+ });
43
+ });
44
+ describe("Result helpers", () => {
45
+ it("builds both variants", () => {
46
+ expect(okResult(3)).toEqual({ ok: true, value: 3 });
47
+ expect(failResult("invalid_input", "Bad URL", { retryable: false, code: "url" })).toEqual({
48
+ ok: false,
49
+ error: {
50
+ kind: "invalid_input",
51
+ message: "Bad URL",
52
+ retryable: false,
53
+ code: "url",
54
+ },
55
+ });
56
+ });
57
+ });