@copilotkit/shared 1.55.3 → 1.56.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/dist/a2ui-prompts.cjs +31 -22
- package/dist/a2ui-prompts.cjs.map +1 -1
- package/dist/a2ui-prompts.d.cts +2 -2
- package/dist/a2ui-prompts.d.cts.map +1 -1
- package/dist/a2ui-prompts.d.mts +2 -2
- package/dist/a2ui-prompts.d.mts.map +1 -1
- package/dist/a2ui-prompts.mjs +31 -22
- package/dist/a2ui-prompts.mjs.map +1 -1
- package/dist/debug.cjs +38 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.cts +29 -0
- package/dist/debug.d.cts.map +1 -0
- package/dist/debug.d.mts +29 -0
- package/dist/debug.d.mts.map +1 -0
- package/dist/debug.mjs +37 -0
- package/dist/debug.mjs.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +137 -30
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/standard-schema.cjs +4 -2
- package/dist/standard-schema.cjs.map +1 -1
- package/dist/standard-schema.d.cts +3 -2
- package/dist/standard-schema.d.cts.map +1 -1
- package/dist/standard-schema.d.mts +3 -2
- package/dist/standard-schema.d.mts.map +1 -1
- package/dist/standard-schema.mjs +4 -2
- package/dist/standard-schema.mjs.map +1 -1
- package/dist/utils/clipboard.cjs +28 -0
- package/dist/utils/clipboard.cjs.map +1 -0
- package/dist/utils/clipboard.d.cts +14 -0
- package/dist/utils/clipboard.d.cts.map +1 -0
- package/dist/utils/clipboard.d.mts +14 -0
- package/dist/utils/clipboard.d.mts.map +1 -0
- package/dist/utils/clipboard.mjs +27 -0
- package/dist/utils/clipboard.mjs.map +1 -0
- package/dist/utils/index.cjs +1 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +1 -0
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/json-schema.cjs +36 -5
- package/dist/utils/json-schema.cjs.map +1 -1
- package/dist/utils/json-schema.d.cts +1 -1
- package/dist/utils/json-schema.d.cts.map +1 -1
- package/dist/utils/json-schema.d.mts +1 -1
- package/dist/utils/json-schema.d.mts.map +1 -1
- package/dist/utils/json-schema.mjs +36 -5
- package/dist/utils/json-schema.mjs.map +1 -1
- package/dist/utils/types.cjs.map +1 -1
- package/dist/utils/types.d.cts +3 -0
- package/dist/utils/types.d.cts.map +1 -1
- package/dist/utils/types.d.mts +3 -0
- package/dist/utils/types.d.mts.map +1 -1
- package/dist/utils/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/debug.test.ts +116 -0
- package/src/__tests__/standard-schema.test.ts +92 -0
- package/src/a2ui-prompts.ts +31 -22
- package/src/debug.ts +55 -0
- package/src/index.ts +1 -0
- package/src/standard-schema.ts +9 -3
- package/src/utils/__tests__/clipboard.test.ts +87 -0
- package/src/utils/__tests__/json-schema.test.ts +250 -1
- package/src/utils/clipboard.ts +23 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/json-schema.ts +84 -3
- package/src/utils/types.ts +3 -0
package/dist/utils/types.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../../src/utils/types.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.mjs","names":[],"sources":["../../src/utils/types.ts"],"sourcesContent":["import type { AgentCapabilities } from \"@ag-ui/core\";\n\nexport type MaybePromise<T> = T | PromiseLike<T>;\n\n/**\n * More specific utility for records with at least one key\n */\nexport type NonEmptyRecord<T> =\n T extends Record<string, unknown>\n ? keyof T extends never\n ? never\n : T\n : never;\n\n/**\n * Type representing an agent's basic information\n */\nexport interface AgentDescription {\n name: string;\n className: string;\n description: string;\n capabilities?: AgentCapabilities;\n}\n\nexport type RuntimeMode = \"sse\" | \"intelligence\";\n\nexport const RUNTIME_MODE_SSE = \"sse\" as const;\nexport const RUNTIME_MODE_INTELLIGENCE = \"intelligence\" as const;\n\nexport interface IntelligenceRuntimeInfo {\n wsUrl: string;\n}\n\nexport type RuntimeLicenseStatus =\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n\nexport interface RuntimeInfo {\n version: string;\n agents: Record<string, AgentDescription>;\n audioFileTranscriptionEnabled: boolean;\n mode: RuntimeMode;\n intelligence?: IntelligenceRuntimeInfo;\n a2uiEnabled?: boolean;\n openGenerativeUIEnabled?: boolean;\n licenseStatus?: RuntimeLicenseStatus;\n}\n"],"mappings":";AA0BA,MAAa,mBAAmB;AAChC,MAAa,4BAA4B"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveDebugConfig } from "../debug";
|
|
3
|
+
|
|
4
|
+
describe("resolveDebugConfig", () => {
|
|
5
|
+
it("returns all-off for undefined", () => {
|
|
6
|
+
expect(resolveDebugConfig(undefined)).toEqual({
|
|
7
|
+
enabled: false,
|
|
8
|
+
events: false,
|
|
9
|
+
lifecycle: false,
|
|
10
|
+
verbose: false,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns all-off for false", () => {
|
|
15
|
+
expect(resolveDebugConfig(false)).toEqual({
|
|
16
|
+
enabled: false,
|
|
17
|
+
events: false,
|
|
18
|
+
lifecycle: false,
|
|
19
|
+
verbose: false,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns events + lifecycle on but verbose off for true (PII safety)", () => {
|
|
24
|
+
expect(resolveDebugConfig(true)).toEqual({
|
|
25
|
+
enabled: true,
|
|
26
|
+
events: true,
|
|
27
|
+
lifecycle: true,
|
|
28
|
+
verbose: false,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("allows explicit verbose opt-in via object shorthand", () => {
|
|
33
|
+
expect(resolveDebugConfig({ verbose: true })).toEqual({
|
|
34
|
+
enabled: true,
|
|
35
|
+
events: true,
|
|
36
|
+
lifecycle: true,
|
|
37
|
+
verbose: true,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("allows explicit verbose opt-in with events", () => {
|
|
42
|
+
expect(resolveDebugConfig({ events: true, verbose: true })).toEqual({
|
|
43
|
+
enabled: true,
|
|
44
|
+
events: true,
|
|
45
|
+
lifecycle: true,
|
|
46
|
+
verbose: true,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("defaults events and lifecycle to true, verbose to false for empty object", () => {
|
|
51
|
+
expect(resolveDebugConfig({})).toEqual({
|
|
52
|
+
enabled: true,
|
|
53
|
+
events: true,
|
|
54
|
+
lifecycle: true,
|
|
55
|
+
verbose: false,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("respects explicit events: true", () => {
|
|
60
|
+
expect(resolveDebugConfig({ events: true })).toEqual({
|
|
61
|
+
enabled: true,
|
|
62
|
+
events: true,
|
|
63
|
+
lifecycle: true,
|
|
64
|
+
verbose: false,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("respects explicit events: false (lifecycle still defaults true)", () => {
|
|
69
|
+
expect(resolveDebugConfig({ events: false })).toEqual({
|
|
70
|
+
enabled: true,
|
|
71
|
+
events: false,
|
|
72
|
+
lifecycle: true,
|
|
73
|
+
verbose: false,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("respects explicit lifecycle: false (events still defaults true)", () => {
|
|
78
|
+
expect(resolveDebugConfig({ lifecycle: false })).toEqual({
|
|
79
|
+
enabled: true,
|
|
80
|
+
events: true,
|
|
81
|
+
lifecycle: false,
|
|
82
|
+
verbose: false,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("sets enabled to false when both events and lifecycle are false", () => {
|
|
87
|
+
expect(resolveDebugConfig({ events: false, lifecycle: false })).toEqual({
|
|
88
|
+
enabled: false,
|
|
89
|
+
events: false,
|
|
90
|
+
lifecycle: false,
|
|
91
|
+
verbose: false,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("clamps verbose to false when enabled is false (events and lifecycle both false)", () => {
|
|
96
|
+
expect(
|
|
97
|
+
resolveDebugConfig({ events: false, lifecycle: false, verbose: true }),
|
|
98
|
+
).toEqual({
|
|
99
|
+
enabled: false,
|
|
100
|
+
events: false,
|
|
101
|
+
lifecycle: false,
|
|
102
|
+
verbose: false,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("handles mixed config: events true, lifecycle false, verbose true", () => {
|
|
107
|
+
expect(
|
|
108
|
+
resolveDebugConfig({ events: true, lifecycle: false, verbose: true }),
|
|
109
|
+
).toEqual({
|
|
110
|
+
enabled: true,
|
|
111
|
+
events: true,
|
|
112
|
+
lifecycle: false,
|
|
113
|
+
verbose: true,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -210,6 +210,98 @@ describe("schemaToJsonSchema", () => {
|
|
|
210
210
|
});
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
describe("Zod v4 schemas (via toJSONSchema method)", () => {
|
|
214
|
+
it("calls toJSONSchema() when the method exists on the schema", () => {
|
|
215
|
+
const expectedOutput = {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: { name: { type: "string" } },
|
|
218
|
+
required: ["name"],
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const mockZod4Schema: StandardSchemaV1 = {
|
|
222
|
+
"~standard": {
|
|
223
|
+
version: 1,
|
|
224
|
+
vendor: "zod",
|
|
225
|
+
validate: (value: unknown) => ({ value }),
|
|
226
|
+
},
|
|
227
|
+
toJSONSchema: () => expectedOutput,
|
|
228
|
+
} as any;
|
|
229
|
+
|
|
230
|
+
const result = schemaToJsonSchema(mockZod4Schema);
|
|
231
|
+
expect(result).toEqual(expectedOutput);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("uses toJSONSchema() even without zodToJsonSchema option", () => {
|
|
235
|
+
const mockZod4Schema: StandardSchemaV1 = {
|
|
236
|
+
"~standard": {
|
|
237
|
+
version: 1,
|
|
238
|
+
vendor: "zod",
|
|
239
|
+
validate: (value: unknown) => ({ value }),
|
|
240
|
+
},
|
|
241
|
+
toJSONSchema: () => ({
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: { city: { type: "string" } },
|
|
244
|
+
}),
|
|
245
|
+
} as any;
|
|
246
|
+
|
|
247
|
+
// No options passed — toJSONSchema() should still work
|
|
248
|
+
const result = schemaToJsonSchema(mockZod4Schema);
|
|
249
|
+
expect(result).toHaveProperty("properties.city.type", "string");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("prefers toJSONSchema() over zodToJsonSchema fallback for Zod v4", () => {
|
|
253
|
+
const mockZod4Schema: StandardSchemaV1 = {
|
|
254
|
+
"~standard": {
|
|
255
|
+
version: 1,
|
|
256
|
+
vendor: "zod",
|
|
257
|
+
validate: (value: unknown) => ({ value }),
|
|
258
|
+
},
|
|
259
|
+
toJSONSchema: () => ({
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: { fromNative: { type: "boolean" } },
|
|
262
|
+
}),
|
|
263
|
+
} as any;
|
|
264
|
+
|
|
265
|
+
const zodFallback = () => ({
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: { fromFallback: { type: "boolean" } },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const result = schemaToJsonSchema(mockZod4Schema, {
|
|
271
|
+
zodToJsonSchema: zodFallback,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(result).toHaveProperty("properties.fromNative");
|
|
275
|
+
expect(result).not.toHaveProperty("properties.fromFallback");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("prefers ~standard.jsonSchema over toJSONSchema()", () => {
|
|
279
|
+
const mockSchema = {
|
|
280
|
+
"~standard": {
|
|
281
|
+
version: 1,
|
|
282
|
+
vendor: "zod",
|
|
283
|
+
validate: (value: unknown) => ({ value }),
|
|
284
|
+
jsonSchema: {
|
|
285
|
+
input: () => ({
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: { fromStandard: { type: "boolean" } },
|
|
288
|
+
}),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
toJSONSchema: () => ({
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: { fromToJSONSchema: { type: "boolean" } },
|
|
294
|
+
}),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const result = schemaToJsonSchema(mockSchema);
|
|
298
|
+
|
|
299
|
+
// Standard JSON Schema V1 should take priority
|
|
300
|
+
expect(result).toHaveProperty("properties.fromStandard");
|
|
301
|
+
expect(result).not.toHaveProperty("properties.fromToJSONSchema");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
213
305
|
describe("Error handling", () => {
|
|
214
306
|
it("throws when schema has no jsonSchema support and no zodToJsonSchema", () => {
|
|
215
307
|
const mockSchema: StandardSchemaV1 = {
|
package/src/a2ui-prompts.ts
CHANGED
|
@@ -47,29 +47,38 @@ CRITICAL: Do NOT use "/name" (absolute) inside templates — use "name" (relativ
|
|
|
47
47
|
The container's path ("/items") uses a leading slash (absolute), but all
|
|
48
48
|
components INSIDE the template use paths WITHOUT leading slash.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
COMPONENT VALUES — DEFAULT RULE:
|
|
51
|
+
Use inline literal values for ALL component properties. Pass strings, numbers,
|
|
52
|
+
arrays, and objects directly on the component. Do NOT use { "path": "..." }
|
|
53
|
+
objects unless the property's schema explicitly allows it (see exception below).
|
|
54
|
+
CRITICAL: USING { "path": "..." } ON A PROPERTY THAT DOES NOT DECLARE PATH
|
|
55
|
+
SUPPORT IN ITS SCHEMA WILL CAUSE A RUNTIME CRASH AND BREAK THE ENTIRE UI.
|
|
56
|
+
ALWAYS CHECK THE COMPONENT SCHEMA FIRST — IF THE PROPERTY ONLY ACCEPTS A
|
|
57
|
+
PLAIN TYPE, YOU MUST USE A LITERAL VALUE.
|
|
58
|
+
VERY IMPORTANT: THE APPLICATION WILL BREAK IF YOU DO NOT FOLLOW THIS RULE!
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
You MUST use { "path": "..." } to make inputs editable.
|
|
60
|
+
For example, a chart's "data" must always be an inline array:
|
|
61
|
+
"data": [{"label": "Jan", "value": 100}, {"label": "Feb", "value": 200}]
|
|
62
|
+
A metric's "value" must always be an inline string:
|
|
63
|
+
"value": "$1,200"
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
PATH BINDING EXCEPTION — SCHEMA-DRIVEN:
|
|
66
|
+
A few properties accept { "path": "/some/path" } as an alternative to a literal
|
|
67
|
+
value. You can identify these in the Available Components schema: the property
|
|
68
|
+
will list BOTH a literal type AND an object-with-path option. If a property only
|
|
69
|
+
shows a single type (string, number, array, etc.), it does NOT support path
|
|
70
|
+
binding — use a literal value only.
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
Path binding is typically used for editable form inputs so the client can write
|
|
73
|
+
user input back to the data model. When building forms:
|
|
74
|
+
- Bind input "value" to a data model path: "value": { "path": "/form/name" }
|
|
75
|
+
- Pre-fill via the "data" tool argument: "data": { "form": { "name": "Alice" } }
|
|
76
|
+
- Capture values on submit via button action context:
|
|
77
|
+
"action": { "event": { "name": "submit", "context": { "name": { "path": "/form/name" } } } }
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
REPEATING CONTENT uses a structural children format (not the same as value binding):
|
|
80
|
+
children: { componentId: "card-id", path: "/items" }
|
|
81
|
+
Components inside templates use RELATIVE paths (no leading slash): { "path": "name" }.`;
|
|
73
82
|
|
|
74
83
|
/**
|
|
75
84
|
* Design guidelines — visual design rules, component hierarchy tips,
|
|
@@ -94,8 +103,8 @@ Design principles:
|
|
|
94
103
|
"action": { "event": { "name": "myAction", "context": { "key": "value" } } }
|
|
95
104
|
The "event" key holds an OBJECT with "name" (required) and "context" (optional).
|
|
96
105
|
Do NOT use a flat format like {"event": "name"} — "event" must be an object.
|
|
97
|
-
- For forms:
|
|
98
|
-
|
|
99
|
-
action context
|
|
106
|
+
- For forms: check the component schema — if an input's "value" property
|
|
107
|
+
supports path binding, use it for editable fields. The submit button's
|
|
108
|
+
action context should reference the same paths to capture user input.
|
|
100
109
|
|
|
101
110
|
Use the SAME surfaceId as the main surface. Match action names to button action event names.`;
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Granular debug configuration for CopilotKit runtime and client.
|
|
3
|
+
* Pass `true` to enable events + lifecycle logging (but NOT verbose payloads),
|
|
4
|
+
* or an object for granular control including `verbose: true` for full payloads.
|
|
5
|
+
*/
|
|
6
|
+
export type DebugConfig =
|
|
7
|
+
| boolean
|
|
8
|
+
| {
|
|
9
|
+
/** Log every event emitted/received. Default: true */
|
|
10
|
+
events?: boolean;
|
|
11
|
+
/** Log request/run lifecycle. Default: true */
|
|
12
|
+
lifecycle?: boolean;
|
|
13
|
+
/** Log full event payloads instead of summaries. Default: false — must be explicitly opted in */
|
|
14
|
+
verbose?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Normalized debug configuration — all fields resolved to booleans. */
|
|
18
|
+
export interface ResolvedDebugConfig {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
events: boolean;
|
|
21
|
+
lifecycle: boolean;
|
|
22
|
+
verbose: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The all-off config used when debug is falsy. */
|
|
26
|
+
const DEBUG_OFF: ResolvedDebugConfig = {
|
|
27
|
+
enabled: false,
|
|
28
|
+
events: false,
|
|
29
|
+
lifecycle: false,
|
|
30
|
+
verbose: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes a DebugConfig value into a ResolvedDebugConfig.
|
|
35
|
+
*
|
|
36
|
+
* - `false` / `undefined` → all off
|
|
37
|
+
* - `true` → events + lifecycle on, verbose off (no PII in logs)
|
|
38
|
+
* - object → merges with defaults (events: true, lifecycle: true, verbose: false)
|
|
39
|
+
*/
|
|
40
|
+
export function resolveDebugConfig(
|
|
41
|
+
debug: DebugConfig | undefined,
|
|
42
|
+
): ResolvedDebugConfig {
|
|
43
|
+
if (!debug) return DEBUG_OFF;
|
|
44
|
+
|
|
45
|
+
if (debug === true) {
|
|
46
|
+
return { enabled: true, events: true, lifecycle: true, verbose: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const events = debug.events ?? true;
|
|
50
|
+
const lifecycle = debug.lifecycle ?? true;
|
|
51
|
+
const enabled = events || lifecycle;
|
|
52
|
+
const verbose = enabled && (debug.verbose ?? false);
|
|
53
|
+
|
|
54
|
+
return { enabled, events, lifecycle, verbose };
|
|
55
|
+
}
|
package/src/index.ts
CHANGED
package/src/standard-schema.ts
CHANGED
|
@@ -48,9 +48,10 @@ function hasStandardJsonSchema(
|
|
|
48
48
|
* Strategy:
|
|
49
49
|
* 1. If the schema implements Standard JSON Schema V1 (`~standard.jsonSchema`),
|
|
50
50
|
* call `schema['~standard'].jsonSchema.input({ target: 'draft-07' })`.
|
|
51
|
-
* 2. If the schema
|
|
51
|
+
* 2. If the schema exposes a `toJSONSchema()` method (Zod v4), call it directly.
|
|
52
|
+
* 3. If the schema is a Zod v3 schema (`~standard.vendor === 'zod'`), use the
|
|
52
53
|
* injected `zodToJsonSchema()` function.
|
|
53
|
-
*
|
|
54
|
+
* 4. Otherwise throw a descriptive error.
|
|
54
55
|
*/
|
|
55
56
|
export function schemaToJsonSchema(
|
|
56
57
|
schema: StandardSchemaV1,
|
|
@@ -61,7 +62,12 @@ export function schemaToJsonSchema(
|
|
|
61
62
|
return schema["~standard"].jsonSchema.input({ target: "draft-07" });
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
// 2. Zod
|
|
65
|
+
// 2. Zod v4 native — exposes toJSONSchema() on the schema itself
|
|
66
|
+
if (typeof (schema as any).toJSONSchema === "function") {
|
|
67
|
+
return (schema as any).toJSONSchema() as Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Zod v3 fallback
|
|
65
71
|
const vendor = schema["~standard"].vendor;
|
|
66
72
|
if (vendor === "zod" && options?.zodToJsonSchema) {
|
|
67
73
|
return options.zodToJsonSchema(schema, { $refStrategy: "none" });
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { copyToClipboard } from "../clipboard";
|
|
3
|
+
|
|
4
|
+
// Mock navigator for Node 20 environments where it doesn't exist
|
|
5
|
+
if (typeof globalThis.navigator === "undefined") {
|
|
6
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
7
|
+
value: { clipboard: { writeText: vi.fn() } },
|
|
8
|
+
writable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("copyToClipboard", () => {
|
|
14
|
+
let originalClipboard: Clipboard;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
originalClipboard = navigator.clipboard;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
22
|
+
value: originalClipboard,
|
|
23
|
+
writable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns true on successful clipboard write", async () => {
|
|
29
|
+
const writeTextMock = vi.fn().mockResolvedValue(undefined);
|
|
30
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
31
|
+
value: { writeText: writeTextMock },
|
|
32
|
+
writable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const result = await copyToClipboard("hello");
|
|
37
|
+
expect(result).toBe(true);
|
|
38
|
+
expect(writeTextMock).toHaveBeenCalledWith("hello");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns false when clipboard API is unavailable", async () => {
|
|
42
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
43
|
+
value: undefined,
|
|
44
|
+
writable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
49
|
+
const result = await copyToClipboard("hello");
|
|
50
|
+
expect(result).toBe(false);
|
|
51
|
+
expect(consoleSpy).toHaveBeenCalledWith("Clipboard API is not available");
|
|
52
|
+
consoleSpy.mockRestore();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns false when writeText is not available", async () => {
|
|
56
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
57
|
+
value: {},
|
|
58
|
+
writable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
63
|
+
const result = await copyToClipboard("hello");
|
|
64
|
+
expect(result).toBe(false);
|
|
65
|
+
consoleSpy.mockRestore();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns false when writeText rejects", async () => {
|
|
69
|
+
const writeTextMock = vi
|
|
70
|
+
.fn()
|
|
71
|
+
.mockRejectedValue(new Error("Permission denied"));
|
|
72
|
+
Object.defineProperty(navigator, "clipboard", {
|
|
73
|
+
value: { writeText: writeTextMock },
|
|
74
|
+
writable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
79
|
+
const result = await copyToClipboard("hello");
|
|
80
|
+
expect(result).toBe(false);
|
|
81
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
82
|
+
"Failed to copy to clipboard:",
|
|
83
|
+
expect.any(Error),
|
|
84
|
+
);
|
|
85
|
+
consoleSpy.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
});
|