@rama_nigg/open-cursor 2.2.1 → 2.3.0
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 +9 -3
- package/src/acp/metrics.ts +83 -0
- package/src/acp/sessions.ts +107 -0
- package/src/acp/tools.ts +209 -0
- package/src/auth.ts +269 -0
- package/src/cli/discover.ts +53 -0
- package/src/cli/model-discovery.ts +50 -0
- package/src/cli/opencode-cursor.ts +620 -0
- package/src/client/simple.ts +277 -0
- package/src/commands/status.ts +39 -0
- package/src/index.ts +40 -0
- package/src/models/config.ts +64 -0
- package/src/models/discovery.ts +132 -0
- package/src/models/index.ts +3 -0
- package/src/models/types.ts +11 -0
- package/src/plugin-entry.ts +28 -0
- package/src/plugin-toggle.ts +67 -0
- package/src/plugin.ts +1918 -0
- package/src/provider/boundary.ts +161 -0
- package/src/provider/runtime-interception.ts +721 -0
- package/src/provider/tool-loop-guard.ts +644 -0
- package/src/provider/tool-schema-compat.ts +516 -0
- package/src/provider.ts +268 -0
- package/src/proxy/formatter.ts +42 -0
- package/src/proxy/handler.ts +29 -0
- package/src/proxy/prompt-builder.ts +171 -0
- package/src/proxy/server.ts +207 -0
- package/src/proxy/tool-loop.ts +317 -0
- package/src/proxy/types.ts +13 -0
- package/src/streaming/ai-sdk-parts.ts +105 -0
- package/src/streaming/delta-tracker.ts +33 -0
- package/src/streaming/line-buffer.ts +44 -0
- package/src/streaming/openai-sse.ts +114 -0
- package/src/streaming/parser.ts +22 -0
- package/src/streaming/types.ts +152 -0
- package/src/tools/core/executor.ts +25 -0
- package/src/tools/core/registry.ts +27 -0
- package/src/tools/core/types.ts +31 -0
- package/src/tools/defaults.ts +673 -0
- package/src/tools/discovery.ts +140 -0
- package/src/tools/executors/cli.ts +58 -0
- package/src/tools/executors/local.ts +25 -0
- package/src/tools/executors/mcp.ts +39 -0
- package/src/tools/executors/sdk.ts +39 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/registry.ts +34 -0
- package/src/tools/router.ts +123 -0
- package/src/tools/schema.ts +58 -0
- package/src/tools/skills/loader.ts +61 -0
- package/src/tools/skills/resolver.ts +21 -0
- package/src/tools/types.ts +29 -0
- package/src/types.ts +8 -0
- package/src/utils/errors.ts +131 -0
- package/src/utils/logger.ts +146 -0
- package/src/utils/perf.ts +44 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import type { OpenAiToolCall } from "../proxy/tool-loop.js";
|
|
2
|
+
|
|
3
|
+
type JsonRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
const EDIT_COMPAT_REPAIR_ENABLED = process.env.CURSOR_ACP_EDIT_COMPAT_REPAIR !== "false";
|
|
6
|
+
|
|
7
|
+
const ARG_KEY_ALIASES = new Map<string, string>([
|
|
8
|
+
["filepath", "path"],
|
|
9
|
+
["filename", "path"],
|
|
10
|
+
["file", "path"],
|
|
11
|
+
["targetpath", "path"],
|
|
12
|
+
["directorypath", "path"],
|
|
13
|
+
["dir", "path"],
|
|
14
|
+
["folder", "path"],
|
|
15
|
+
["directory", "path"],
|
|
16
|
+
["targetdirectory", "path"],
|
|
17
|
+
["targetfile", "path"],
|
|
18
|
+
["globpattern", "pattern"],
|
|
19
|
+
["filepattern", "pattern"],
|
|
20
|
+
["searchpattern", "pattern"],
|
|
21
|
+
["includepattern", "include"],
|
|
22
|
+
["workingdirectory", "cwd"],
|
|
23
|
+
["workdir", "cwd"],
|
|
24
|
+
["currentdirectory", "cwd"],
|
|
25
|
+
["cmd", "command"],
|
|
26
|
+
["script", "command"],
|
|
27
|
+
["shellcommand", "command"],
|
|
28
|
+
["terminalcommand", "command"],
|
|
29
|
+
["contents", "content"],
|
|
30
|
+
["text", "content"],
|
|
31
|
+
["body", "content"],
|
|
32
|
+
["data", "content"],
|
|
33
|
+
["payload", "content"],
|
|
34
|
+
["streamcontent", "content"],
|
|
35
|
+
["recursive", "force"],
|
|
36
|
+
["oldstring", "old_string"],
|
|
37
|
+
["newstring", "new_string"],
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export interface ToolSchemaValidationResult {
|
|
41
|
+
hasSchema: boolean;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
missing: string[];
|
|
44
|
+
unexpected: string[];
|
|
45
|
+
typeErrors: string[];
|
|
46
|
+
repairHint?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ToolSchemaCompatResult {
|
|
50
|
+
toolCall: OpenAiToolCall;
|
|
51
|
+
normalizedArgs: JsonRecord;
|
|
52
|
+
originalArgKeys: string[];
|
|
53
|
+
normalizedArgKeys: string[];
|
|
54
|
+
collisionKeys: string[];
|
|
55
|
+
validation: ToolSchemaValidationResult;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildToolSchemaMap(tools: Array<unknown>): Map<string, unknown> {
|
|
59
|
+
const schemas = new Map<string, unknown>();
|
|
60
|
+
for (const rawTool of tools) {
|
|
61
|
+
const tool = isRecord(rawTool) ? rawTool : null;
|
|
62
|
+
if (!tool) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const fn = isRecord(tool.function) ? tool.function : tool;
|
|
66
|
+
const name = typeof fn.name === "string" ? fn.name.trim() : "";
|
|
67
|
+
if (!name) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (fn.parameters !== undefined) {
|
|
71
|
+
schemas.set(name, fn.parameters);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return schemas;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function applyToolSchemaCompat(
|
|
78
|
+
toolCall: OpenAiToolCall,
|
|
79
|
+
toolSchemaMap: Map<string, unknown>,
|
|
80
|
+
): ToolSchemaCompatResult {
|
|
81
|
+
const parsedArgs = parseArguments(toolCall.function.arguments);
|
|
82
|
+
const originalArgKeys = Object.keys(parsedArgs);
|
|
83
|
+
const { normalizedArgs, collisionKeys } = normalizeArgumentKeys(parsedArgs);
|
|
84
|
+
const toolSpecificArgs = normalizeToolSpecificArgs(toolCall.function.name, normalizedArgs);
|
|
85
|
+
const schema = toolSchemaMap.get(toolCall.function.name);
|
|
86
|
+
const sanitization = sanitizeArgumentsForSchema(toolSpecificArgs, schema);
|
|
87
|
+
const validation = validateToolArguments(
|
|
88
|
+
toolCall.function.name,
|
|
89
|
+
sanitization.args,
|
|
90
|
+
schema,
|
|
91
|
+
sanitization.unexpected,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const normalizedToolCall: OpenAiToolCall = {
|
|
95
|
+
...toolCall,
|
|
96
|
+
function: {
|
|
97
|
+
...toolCall.function,
|
|
98
|
+
arguments: JSON.stringify(sanitization.args),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
toolCall: normalizedToolCall,
|
|
104
|
+
normalizedArgs: sanitization.args,
|
|
105
|
+
originalArgKeys,
|
|
106
|
+
normalizedArgKeys: Object.keys(sanitization.args),
|
|
107
|
+
collisionKeys,
|
|
108
|
+
validation,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseArguments(rawArguments: string): JsonRecord {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(rawArguments);
|
|
115
|
+
if (isRecord(parsed)) {
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
return { value: parsed };
|
|
119
|
+
} catch {
|
|
120
|
+
return { value: rawArguments };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeArgumentKeys(args: JsonRecord): {
|
|
125
|
+
normalizedArgs: JsonRecord;
|
|
126
|
+
collisionKeys: string[];
|
|
127
|
+
} {
|
|
128
|
+
const normalizedArgs: JsonRecord = { ...args };
|
|
129
|
+
const collisionKeys: string[] = [];
|
|
130
|
+
|
|
131
|
+
for (const [rawKey, rawValue] of Object.entries(args)) {
|
|
132
|
+
const canonicalKey = resolveCanonicalArgKey(rawKey);
|
|
133
|
+
if (!canonicalKey || canonicalKey === rawKey) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const canonicalInOriginal = hasOwn(args, canonicalKey);
|
|
138
|
+
const canonicalInNormalized = hasOwn(normalizedArgs, canonicalKey);
|
|
139
|
+
if (canonicalInOriginal || canonicalInNormalized) {
|
|
140
|
+
collisionKeys.push(rawKey);
|
|
141
|
+
delete normalizedArgs[rawKey];
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
normalizedArgs[canonicalKey] = rawValue;
|
|
146
|
+
delete normalizedArgs[rawKey];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { normalizedArgs, collisionKeys };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveCanonicalArgKey(rawKey: string): string | null {
|
|
153
|
+
const token = rawKey.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
154
|
+
return ARG_KEY_ALIASES.get(token) ?? null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeToolSpecificArgs(toolName: string, args: JsonRecord): JsonRecord {
|
|
158
|
+
const normalizedToolName = toolName.toLowerCase();
|
|
159
|
+
if (normalizedToolName === "bash") {
|
|
160
|
+
const normalized: JsonRecord = { ...args };
|
|
161
|
+
const normalizedCommand = normalizeBashCommand(normalized.command);
|
|
162
|
+
if (typeof normalizedCommand === "string" && normalizedCommand.trim().length > 0) {
|
|
163
|
+
normalized.command = normalizedCommand;
|
|
164
|
+
}
|
|
165
|
+
if (
|
|
166
|
+
normalized.cwd === undefined
|
|
167
|
+
&& typeof normalized.path === "string"
|
|
168
|
+
&& normalized.path.trim().length > 0
|
|
169
|
+
) {
|
|
170
|
+
normalized.cwd = normalized.path;
|
|
171
|
+
}
|
|
172
|
+
return normalized;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (normalizedToolName === "rm") {
|
|
176
|
+
const normalized: JsonRecord = { ...args };
|
|
177
|
+
if (typeof normalized.force === "string") {
|
|
178
|
+
const lowered = normalized.force.trim().toLowerCase();
|
|
179
|
+
if (lowered === "true" || lowered === "1" || lowered === "yes") {
|
|
180
|
+
normalized.force = true;
|
|
181
|
+
} else if (lowered === "false" || lowered === "0" || lowered === "no") {
|
|
182
|
+
normalized.force = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return normalized;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (normalizedToolName === "todowrite") {
|
|
189
|
+
if (!Array.isArray(args.todos)) {
|
|
190
|
+
return args;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const todos = args.todos.map((entry) => {
|
|
194
|
+
if (!isRecord(entry)) {
|
|
195
|
+
return entry;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const todo: JsonRecord = { ...entry };
|
|
199
|
+
if (typeof todo.status === "string") {
|
|
200
|
+
todo.status = normalizeTodoStatus(todo.status);
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
todo.priority === undefined
|
|
204
|
+
|| todo.priority === null
|
|
205
|
+
|| (typeof todo.priority === "string" && todo.priority.trim().length === 0)
|
|
206
|
+
) {
|
|
207
|
+
todo.priority = "medium";
|
|
208
|
+
}
|
|
209
|
+
return todo;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
...args,
|
|
214
|
+
todos,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (normalizedToolName === "write") {
|
|
219
|
+
const normalized: JsonRecord = { ...args };
|
|
220
|
+
|
|
221
|
+
// Some model variants confuse write/edit and send edit-style payload keys.
|
|
222
|
+
// Map them into canonical write arguments before schema validation/sanitization.
|
|
223
|
+
if (normalized.content === undefined && normalized.new_string !== undefined) {
|
|
224
|
+
const coerced = coerceToString(normalized.new_string);
|
|
225
|
+
if (coerced !== null) {
|
|
226
|
+
normalized.content = coerced;
|
|
227
|
+
}
|
|
228
|
+
delete normalized.new_string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (normalized.content !== undefined && typeof normalized.content !== "string") {
|
|
232
|
+
const coerced = coerceToString(normalized.content);
|
|
233
|
+
if (coerced !== null) {
|
|
234
|
+
normalized.content = coerced;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return normalized;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (normalizedToolName !== "edit" || !EDIT_COMPAT_REPAIR_ENABLED) {
|
|
242
|
+
return args;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const repaired: JsonRecord = { ...args };
|
|
246
|
+
const hasStringNew = typeof repaired.new_string === "string";
|
|
247
|
+
const hasStringOld = typeof repaired.old_string === "string";
|
|
248
|
+
|
|
249
|
+
// Coerce non-string content/streamContent into a string before repair.
|
|
250
|
+
// Models frequently emit array-of-chunks (streamContent) or object payloads.
|
|
251
|
+
if (repaired.content !== undefined && typeof repaired.content !== "string") {
|
|
252
|
+
const coerced = coerceToString(repaired.content);
|
|
253
|
+
if (coerced !== null) {
|
|
254
|
+
repaired.content = coerced;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const content = repaired.content;
|
|
259
|
+
|
|
260
|
+
// Guarded compatibility repair for models that send full-content edit payloads.
|
|
261
|
+
if (!hasStringNew && typeof content === "string") {
|
|
262
|
+
repaired.new_string = content;
|
|
263
|
+
}
|
|
264
|
+
if (typeof repaired.new_string === "string" && !hasStringOld) {
|
|
265
|
+
repaired.old_string = "";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return repaired;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizeBashCommand(value: unknown): string | null {
|
|
272
|
+
if (typeof value === "string") {
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(value)) {
|
|
276
|
+
const parts = value
|
|
277
|
+
.map((entry) => (typeof entry === "string" ? entry : coerceToString(entry)))
|
|
278
|
+
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
|
|
279
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
280
|
+
}
|
|
281
|
+
if (isRecord(value)) {
|
|
282
|
+
const command = typeof value.command === "string" ? value.command : null;
|
|
283
|
+
const args = Array.isArray(value.args)
|
|
284
|
+
? value.args
|
|
285
|
+
.map((entry) => (typeof entry === "string" ? entry : coerceToString(entry)))
|
|
286
|
+
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
|
287
|
+
: [];
|
|
288
|
+
if (command && args.length > 0) {
|
|
289
|
+
return [command, ...args].join(" ");
|
|
290
|
+
}
|
|
291
|
+
if (command) {
|
|
292
|
+
return command;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizeTodoStatus(status: string): string {
|
|
299
|
+
const normalized = status.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
300
|
+
if (normalized === "todo_status_pending") {
|
|
301
|
+
return "pending";
|
|
302
|
+
}
|
|
303
|
+
if (normalized === "todo_status_inprogress" || normalized === "todo_status_in_progress") {
|
|
304
|
+
return "in_progress";
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
normalized === "todo_status_done"
|
|
308
|
+
|| normalized === "todo_status_complete"
|
|
309
|
+
|| normalized === "todo_status_completed"
|
|
310
|
+
) {
|
|
311
|
+
return "completed";
|
|
312
|
+
}
|
|
313
|
+
if (normalized === "todo" || normalized === "pending") {
|
|
314
|
+
return "pending";
|
|
315
|
+
}
|
|
316
|
+
if (normalized === "inprogress" || normalized === "in_progress") {
|
|
317
|
+
return "in_progress";
|
|
318
|
+
}
|
|
319
|
+
if (normalized === "done" || normalized === "complete" || normalized === "completed") {
|
|
320
|
+
return "completed";
|
|
321
|
+
}
|
|
322
|
+
return status;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function sanitizeArgumentsForSchema(
|
|
326
|
+
args: JsonRecord,
|
|
327
|
+
schema: unknown,
|
|
328
|
+
): { args: JsonRecord; unexpected: string[] } {
|
|
329
|
+
if (!isRecord(schema)) {
|
|
330
|
+
return { args, unexpected: [] };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (schema.additionalProperties !== false) {
|
|
334
|
+
return { args, unexpected: [] };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
338
|
+
const propertyNames = new Set(Object.keys(properties));
|
|
339
|
+
const sanitized: JsonRecord = {};
|
|
340
|
+
const unexpected: string[] = [];
|
|
341
|
+
|
|
342
|
+
for (const [key, value] of Object.entries(args)) {
|
|
343
|
+
if (propertyNames.has(key)) {
|
|
344
|
+
sanitized[key] = value;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
unexpected.push(key);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { args: sanitized, unexpected };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function validateToolArguments(
|
|
354
|
+
toolName: string,
|
|
355
|
+
args: JsonRecord,
|
|
356
|
+
schema: unknown,
|
|
357
|
+
unexpected: string[],
|
|
358
|
+
): ToolSchemaValidationResult {
|
|
359
|
+
if (!isRecord(schema)) {
|
|
360
|
+
return {
|
|
361
|
+
hasSchema: false,
|
|
362
|
+
ok: true,
|
|
363
|
+
missing: [],
|
|
364
|
+
unexpected: [],
|
|
365
|
+
typeErrors: [],
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
370
|
+
const required = Array.isArray(schema.required)
|
|
371
|
+
? schema.required.filter((value): value is string => typeof value === "string")
|
|
372
|
+
: [];
|
|
373
|
+
const missing = required.filter((key) => !hasOwn(args, key));
|
|
374
|
+
|
|
375
|
+
const typeErrors: string[] = [];
|
|
376
|
+
for (const [key, value] of Object.entries(args)) {
|
|
377
|
+
const propertySchema = properties[key];
|
|
378
|
+
if (!isRecord(propertySchema)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (!matchesType(value, propertySchema.type)) {
|
|
382
|
+
if (propertySchema.type !== undefined) {
|
|
383
|
+
typeErrors.push(`${key}: expected ${String(propertySchema.type)}`);
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (
|
|
388
|
+
Array.isArray(propertySchema.enum)
|
|
389
|
+
&& !propertySchema.enum.some((candidate) => Object.is(candidate, value))
|
|
390
|
+
) {
|
|
391
|
+
typeErrors.push(`${key}: expected enum ${JSON.stringify(propertySchema.enum)}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const ok = missing.length === 0 && typeErrors.length === 0;
|
|
396
|
+
return {
|
|
397
|
+
hasSchema: true,
|
|
398
|
+
ok,
|
|
399
|
+
missing,
|
|
400
|
+
unexpected,
|
|
401
|
+
typeErrors,
|
|
402
|
+
repairHint: ok ? undefined : buildRepairHint(toolName, missing, unexpected, typeErrors),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildRepairHint(
|
|
407
|
+
toolName: string,
|
|
408
|
+
missing: string[],
|
|
409
|
+
unexpected: string[],
|
|
410
|
+
typeErrors: string[],
|
|
411
|
+
): string {
|
|
412
|
+
const hints: string[] = [];
|
|
413
|
+
if (missing.length > 0) {
|
|
414
|
+
hints.push(`missing required: ${missing.join(", ")}`);
|
|
415
|
+
}
|
|
416
|
+
if (unexpected.length > 0) {
|
|
417
|
+
hints.push(`remove unsupported fields: ${unexpected.join(", ")}`);
|
|
418
|
+
}
|
|
419
|
+
if (typeErrors.length > 0) {
|
|
420
|
+
hints.push(`fix type errors: ${typeErrors.join("; ")}`);
|
|
421
|
+
}
|
|
422
|
+
if (
|
|
423
|
+
toolName.toLowerCase() === "edit"
|
|
424
|
+
&& (missing.includes("old_string") || missing.includes("new_string"))
|
|
425
|
+
) {
|
|
426
|
+
hints.push("edit requires path, old_string, and new_string");
|
|
427
|
+
}
|
|
428
|
+
return hints.join(" | ");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function matchesType(value: unknown, schemaType: unknown): boolean {
|
|
432
|
+
if (schemaType === undefined) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
if (Array.isArray(schemaType)) {
|
|
436
|
+
return schemaType.some((entry) => matchesType(value, entry));
|
|
437
|
+
}
|
|
438
|
+
if (typeof schemaType !== "string") {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
switch (schemaType) {
|
|
442
|
+
case "string":
|
|
443
|
+
return typeof value === "string";
|
|
444
|
+
case "number":
|
|
445
|
+
return typeof value === "number";
|
|
446
|
+
case "integer":
|
|
447
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
448
|
+
case "boolean":
|
|
449
|
+
return typeof value === "boolean";
|
|
450
|
+
case "object":
|
|
451
|
+
return isRecord(value);
|
|
452
|
+
case "array":
|
|
453
|
+
return Array.isArray(value);
|
|
454
|
+
case "null":
|
|
455
|
+
return value === null;
|
|
456
|
+
default:
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function coerceToString(value: unknown): string | null {
|
|
462
|
+
if (typeof value === "string") {
|
|
463
|
+
return value;
|
|
464
|
+
}
|
|
465
|
+
if (value === null || value === undefined) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
if (Array.isArray(value)) {
|
|
469
|
+
const parts: string[] = [];
|
|
470
|
+
for (const item of value) {
|
|
471
|
+
if (typeof item === "string") {
|
|
472
|
+
parts.push(item);
|
|
473
|
+
} else if (isRecord(item)) {
|
|
474
|
+
const text = typeof item.text === "string"
|
|
475
|
+
? item.text
|
|
476
|
+
: typeof item.content === "string"
|
|
477
|
+
? item.content
|
|
478
|
+
: typeof item.value === "string"
|
|
479
|
+
? item.value
|
|
480
|
+
: null;
|
|
481
|
+
if (text !== null) {
|
|
482
|
+
parts.push(text);
|
|
483
|
+
} else {
|
|
484
|
+
parts.push(JSON.stringify(item));
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
parts.push(String(item));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return parts.length > 0 ? parts.join("") : null;
|
|
491
|
+
}
|
|
492
|
+
if (isRecord(value)) {
|
|
493
|
+
if (typeof value.text === "string") {
|
|
494
|
+
return value.text;
|
|
495
|
+
}
|
|
496
|
+
if (typeof value.content === "string") {
|
|
497
|
+
return value.content;
|
|
498
|
+
}
|
|
499
|
+
if (typeof value.value === "string") {
|
|
500
|
+
return value.value;
|
|
501
|
+
}
|
|
502
|
+
return JSON.stringify(value);
|
|
503
|
+
}
|
|
504
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
505
|
+
return String(value);
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function hasOwn(record: JsonRecord, key: string): boolean {
|
|
511
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
515
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
516
|
+
}
|