File size: 4,084 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// src/app/(dashboard)/dashboard/playground/hooks/useStructuredOutput.ts
"use client";

import { useState, useCallback } from "react";
import { StructuredOutputSchema } from "@/shared/schemas/playground";

export interface StructuredOutputSchemaInput {
  name: string;
  schema: Record<string, unknown>;
  strict?: boolean;
}

export interface UseStructuredOutputState {
  enabled: boolean;
  schema: StructuredOutputSchemaInput | null;
  error: string | null;
}

export interface ValidateResponseResult {
  valid: boolean;
  error?: string;
}

export interface UseStructuredOutput extends UseStructuredOutputState {
  setEnabled: (enabled: boolean) => void;
  /**
   * Set and validate the JSON schema via Zod StructuredOutputSchema.
   * Clears error on success; sets error on validation failure.
   */
  setSchema: (s: StructuredOutputSchemaInput) => void;
  /**
   * Validate a response body against the current schema.
   * - First tries JSON.parse if content is a string.
   * - Then checks that the parsed value is a non-null object (basic shape validation).
   * Returns { valid: true } on success; { valid: false, error } on failure.
   */
  validateResponse: (content: unknown) => ValidateResponseResult;
}

/**
 * Hook for the Structured Output (JSON mode) toggle in the Build tab.
 * Manages toggle state + JSON schema + client-side response validation.
 */
export function useStructuredOutput(): UseStructuredOutput {
  const [enabled, setEnabledState] = useState(false);
  const [schema, setSchemaState] = useState<StructuredOutputSchemaInput | null>(null);
  const [error, setError] = useState<string | null>(null);

  const setEnabled = useCallback((value: boolean): void => {
    setEnabledState(value);
  }, []);

  const setSchema = useCallback((s: StructuredOutputSchemaInput): void => {
    // Validate via StructuredOutputSchema (wraps the input as if it were a full response_format object).
    const parsed = StructuredOutputSchema.safeParse({
      type: "json_schema",
      json_schema: s,
    });
    if (!parsed.success) {
      const message = parsed.error.issues.map((i) => i.message).join("; ");
      setError(message);
      return;
    }
    setSchemaState(s);
    setError(null);
  }, []);

  const validateResponse = useCallback(
    (content: unknown): ValidateResponseResult => {
      if (schema == null) {
        return { valid: false, error: "No schema set" };
      }

      // Step 1: parse JSON if the content is a string.
      let parsed: unknown = content;
      if (typeof content === "string") {
        try {
          parsed = JSON.parse(content);
        } catch {
          return { valid: false, error: "Response is not valid JSON" };
        }
      }

      // Step 2: basic shape validation — must be a non-null object.
      // Full JSON Schema validation would require ajv (not present in this project).
      // We do a structural check: verify it's an object, and for each key in the
      // schema's `properties` (if present), check that the key exists in the response.
      if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
        return { valid: false, error: "Response is not a JSON object" };
      }

      const responseObj = parsed as Record<string, unknown>;
      const schemaObj = schema.schema;

      // If the schema has a "properties" field, validate that required keys are present.
      const properties = schemaObj["properties"];
      if (properties != null && typeof properties === "object" && !Array.isArray(properties)) {
        const required = schemaObj["required"];
        if (Array.isArray(required)) {
          for (const key of required) {
            if (typeof key === "string" && !(key in responseObj)) {
              return {
                valid: false,
                error: `Response is missing required field: "${key}"`,
              };
            }
          }
        }
      }

      return { valid: true };
    },
    [schema],
  );

  return {
    enabled,
    schema,
    error,
    setEnabled,
    setSchema,
    validateResponse,
  };
}