@rce-mcp/contracts 0.1.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/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +778 -0
- package/dist/index.js +234 -0
- package/package.json +13 -0
- package/src/index.ts +281 -0
- package/test/benchmark-assets.test.ts +44 -0
- package/test/contracts.test.ts +100 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +4 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const API_VERSION = "v1";
|
|
3
|
+
export const PublicToolNameSchema = z.enum(["search_context", "enhance_prompt"]);
|
|
4
|
+
export const ErrorCodeSchema = z.enum([
|
|
5
|
+
"INVALID_ARGUMENT",
|
|
6
|
+
"UNAUTHENTICATED",
|
|
7
|
+
"FORBIDDEN",
|
|
8
|
+
"NOT_FOUND",
|
|
9
|
+
"RATE_LIMITED",
|
|
10
|
+
"CONFLICT",
|
|
11
|
+
"UPSTREAM_FAILURE",
|
|
12
|
+
"INTERNAL"
|
|
13
|
+
]);
|
|
14
|
+
export const ErrorEnvelopeSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
error: z
|
|
17
|
+
.object({
|
|
18
|
+
code: ErrorCodeSchema,
|
|
19
|
+
message: z.string().min(1),
|
|
20
|
+
retryable: z.boolean(),
|
|
21
|
+
trace_id: z.string().min(1),
|
|
22
|
+
details: z.record(z.unknown()).optional()
|
|
23
|
+
})
|
|
24
|
+
.strict()
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
export const SearchContextFiltersSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
language: z.string().max(50).optional(),
|
|
30
|
+
path_prefix: z.string().max(1024).optional(),
|
|
31
|
+
glob: z.string().max(256).optional()
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
export const SearchContextInputSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
project_root_path: z.string().min(1).max(1024),
|
|
37
|
+
query: z.string().min(3).max(4000),
|
|
38
|
+
top_k: z.number().int().min(1).max(20).default(8),
|
|
39
|
+
filters: SearchContextFiltersSchema.optional()
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
export const SearchContextResultSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
path: z.string(),
|
|
45
|
+
start_line: z.number().int().min(1),
|
|
46
|
+
end_line: z.number().int().min(1),
|
|
47
|
+
snippet: z.string(),
|
|
48
|
+
score: z.number(),
|
|
49
|
+
reason: z.string()
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
export const SearchContextOutputSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
trace_id: z.string().min(1),
|
|
55
|
+
results: z.array(SearchContextResultSchema),
|
|
56
|
+
search_metadata: z
|
|
57
|
+
.object({
|
|
58
|
+
latency_ms: z.number().int().min(0),
|
|
59
|
+
retrieval_mode: z.enum(["hybrid", "vector", "lexical"]),
|
|
60
|
+
index_version: z.string()
|
|
61
|
+
})
|
|
62
|
+
.strict()
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
export const SyncPushFileSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
path: z.string().min(1).max(2048),
|
|
68
|
+
content: z.string().max(1_000_000),
|
|
69
|
+
language: z.string().min(1).max(50).optional(),
|
|
70
|
+
generated: z.boolean().optional(),
|
|
71
|
+
binary: z.boolean().optional(),
|
|
72
|
+
updated_at: z.string().max(64).optional()
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
export const SyncPushInputSchema = z
|
|
76
|
+
.object({
|
|
77
|
+
project_root_path: z.string().min(1).max(1024),
|
|
78
|
+
workspace_id: z.string().min(1).max(128).optional(),
|
|
79
|
+
index_version: z.string().min(1).max(128).optional(),
|
|
80
|
+
files: z.array(SyncPushFileSchema).min(1).max(10_000)
|
|
81
|
+
})
|
|
82
|
+
.strict();
|
|
83
|
+
export const SyncPushDeltaInputSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
project_root_path: z.string().min(1).max(1024),
|
|
86
|
+
workspace_id: z.string().min(1).max(128).optional(),
|
|
87
|
+
base_index_version: z.string().min(1).max(128).optional(),
|
|
88
|
+
upsert_files: z.array(SyncPushFileSchema).max(10_000).default([]),
|
|
89
|
+
deleted_paths: z.array(z.string().min(1).max(2048)).max(10_000).default([])
|
|
90
|
+
})
|
|
91
|
+
.strict()
|
|
92
|
+
.superRefine((value, ctx) => {
|
|
93
|
+
if (value.upsert_files.length + value.deleted_paths.length <= 0) {
|
|
94
|
+
ctx.addIssue({
|
|
95
|
+
code: z.ZodIssueCode.custom,
|
|
96
|
+
message: "at least one delta operation is required",
|
|
97
|
+
path: ["upsert_files"]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
export const SyncPushReportSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
status: z.string(),
|
|
104
|
+
counts: z
|
|
105
|
+
.object({
|
|
106
|
+
added: z.number().int().min(0),
|
|
107
|
+
modified: z.number().int().min(0),
|
|
108
|
+
deleted: z.number().int().min(0),
|
|
109
|
+
unchanged: z.number().int().min(0),
|
|
110
|
+
skipped: z.number().int().min(0)
|
|
111
|
+
})
|
|
112
|
+
.strict()
|
|
113
|
+
})
|
|
114
|
+
.strict();
|
|
115
|
+
export const SyncPushResponseSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
trace_id: z.string().min(1),
|
|
118
|
+
tenant_id: z.string().min(1),
|
|
119
|
+
workspace_id: z.string().min(1).max(128),
|
|
120
|
+
project_root_path: z.string().min(1).max(1024),
|
|
121
|
+
index_version: z.string().min(1).max(128),
|
|
122
|
+
workspace_created: z.boolean(),
|
|
123
|
+
report: SyncPushReportSchema
|
|
124
|
+
})
|
|
125
|
+
.strict();
|
|
126
|
+
export const SyncPushDeltaResponseSchema = SyncPushResponseSchema.extend({
|
|
127
|
+
base_index_version: z.string().min(1).max(128).nullable(),
|
|
128
|
+
applied_delta: z
|
|
129
|
+
.object({
|
|
130
|
+
upsert_files: z.number().int().min(0),
|
|
131
|
+
deleted_paths: z.number().int().min(0)
|
|
132
|
+
})
|
|
133
|
+
.strict()
|
|
134
|
+
}).strict();
|
|
135
|
+
export const ConversationMessageSchema = z
|
|
136
|
+
.object({
|
|
137
|
+
role: z.enum(["user", "assistant", "system"]),
|
|
138
|
+
content: z.string().max(4000)
|
|
139
|
+
})
|
|
140
|
+
.strict();
|
|
141
|
+
export const EnhancePromptInputSchema = z
|
|
142
|
+
.object({
|
|
143
|
+
prompt: z.string().min(3).max(12000),
|
|
144
|
+
conversation_history: z.array(ConversationMessageSchema).max(30),
|
|
145
|
+
project_root_path: z.string().max(1024).optional()
|
|
146
|
+
})
|
|
147
|
+
.strict();
|
|
148
|
+
export const ContextRefSchema = z
|
|
149
|
+
.object({
|
|
150
|
+
path: z.string(),
|
|
151
|
+
start_line: z.number().int().min(1),
|
|
152
|
+
end_line: z.number().int().min(1),
|
|
153
|
+
reason: z.string()
|
|
154
|
+
})
|
|
155
|
+
.strict();
|
|
156
|
+
export const EnhancePromptOutputSchema = z
|
|
157
|
+
.object({
|
|
158
|
+
trace_id: z.string().min(1),
|
|
159
|
+
enhanced_prompt: z.string(),
|
|
160
|
+
context_refs: z.array(ContextRefSchema),
|
|
161
|
+
warnings: z.array(z.string()),
|
|
162
|
+
questions: z.array(z.string())
|
|
163
|
+
})
|
|
164
|
+
.strict();
|
|
165
|
+
export const RetrievalBenchmarkRepoSchema = z
|
|
166
|
+
.object({
|
|
167
|
+
id: z.string().min(1).max(100),
|
|
168
|
+
git_url: z.string().min(1).max(2000),
|
|
169
|
+
commit: z.string().regex(/^[0-9a-f]{7,40}$/i),
|
|
170
|
+
split: z.enum(["train", "dev", "test"]),
|
|
171
|
+
language: z.string().max(50).optional(),
|
|
172
|
+
notes: z.string().max(500).optional()
|
|
173
|
+
})
|
|
174
|
+
.strict();
|
|
175
|
+
export const RetrievalBenchmarkManifestSchema = z
|
|
176
|
+
.object({
|
|
177
|
+
schema_version: z.literal("v1"),
|
|
178
|
+
generated_at: z.string().datetime().optional(),
|
|
179
|
+
repos: z.array(RetrievalBenchmarkRepoSchema).min(1)
|
|
180
|
+
})
|
|
181
|
+
.strict();
|
|
182
|
+
export const RetrievalBenchmarkQuerySchema = z
|
|
183
|
+
.object({
|
|
184
|
+
id: z.string().min(1).max(120),
|
|
185
|
+
repo_id: z.string().min(1).max(100),
|
|
186
|
+
tool: z.enum(["search_context", "enhance_prompt"]).default("search_context"),
|
|
187
|
+
query: z.string().min(3).max(4000),
|
|
188
|
+
expected_paths: z.array(z.string().min(1).max(2048)).min(1),
|
|
189
|
+
tags: z.array(z.string().min(1).max(50)).max(12).optional(),
|
|
190
|
+
notes: z.string().max(500).optional()
|
|
191
|
+
})
|
|
192
|
+
.strict();
|
|
193
|
+
export function isPublicToolName(value) {
|
|
194
|
+
return PublicToolNameSchema.safeParse(value).success;
|
|
195
|
+
}
|
|
196
|
+
export function toValidationDetails(error) {
|
|
197
|
+
const issue = error.issues[0];
|
|
198
|
+
return {
|
|
199
|
+
field: issue?.path.join(".") ?? "unknown",
|
|
200
|
+
reason: issue?.message ?? "invalid input"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export function makeErrorEnvelope(input) {
|
|
204
|
+
return {
|
|
205
|
+
error: {
|
|
206
|
+
code: input.code,
|
|
207
|
+
message: input.message,
|
|
208
|
+
retryable: input.retryable ?? false,
|
|
209
|
+
trace_id: input.trace_id,
|
|
210
|
+
...(input.details ? { details: input.details } : {})
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export function errorCodeToHttpStatus(code) {
|
|
215
|
+
switch (code) {
|
|
216
|
+
case "INVALID_ARGUMENT":
|
|
217
|
+
return 400;
|
|
218
|
+
case "UNAUTHENTICATED":
|
|
219
|
+
return 401;
|
|
220
|
+
case "FORBIDDEN":
|
|
221
|
+
return 403;
|
|
222
|
+
case "NOT_FOUND":
|
|
223
|
+
return 404;
|
|
224
|
+
case "CONFLICT":
|
|
225
|
+
return 409;
|
|
226
|
+
case "RATE_LIMITED":
|
|
227
|
+
return 429;
|
|
228
|
+
case "UPSTREAM_FAILURE":
|
|
229
|
+
return 503;
|
|
230
|
+
case "INTERNAL":
|
|
231
|
+
default:
|
|
232
|
+
return 500;
|
|
233
|
+
}
|
|
234
|
+
}
|
package/package.json
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const API_VERSION = "v1" as const;
|
|
4
|
+
|
|
5
|
+
export const PublicToolNameSchema = z.enum(["search_context", "enhance_prompt"]);
|
|
6
|
+
export type PublicToolName = z.infer<typeof PublicToolNameSchema>;
|
|
7
|
+
|
|
8
|
+
export const ErrorCodeSchema = z.enum([
|
|
9
|
+
"INVALID_ARGUMENT",
|
|
10
|
+
"UNAUTHENTICATED",
|
|
11
|
+
"FORBIDDEN",
|
|
12
|
+
"NOT_FOUND",
|
|
13
|
+
"RATE_LIMITED",
|
|
14
|
+
"CONFLICT",
|
|
15
|
+
"UPSTREAM_FAILURE",
|
|
16
|
+
"INTERNAL"
|
|
17
|
+
]);
|
|
18
|
+
export type ErrorCode = z.infer<typeof ErrorCodeSchema>;
|
|
19
|
+
|
|
20
|
+
export const ErrorEnvelopeSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
error: z
|
|
23
|
+
.object({
|
|
24
|
+
code: ErrorCodeSchema,
|
|
25
|
+
message: z.string().min(1),
|
|
26
|
+
retryable: z.boolean(),
|
|
27
|
+
trace_id: z.string().min(1),
|
|
28
|
+
details: z.record(z.unknown()).optional()
|
|
29
|
+
})
|
|
30
|
+
.strict()
|
|
31
|
+
})
|
|
32
|
+
.strict();
|
|
33
|
+
export type ErrorEnvelope = z.infer<typeof ErrorEnvelopeSchema>;
|
|
34
|
+
|
|
35
|
+
export const SearchContextFiltersSchema = z
|
|
36
|
+
.object({
|
|
37
|
+
language: z.string().max(50).optional(),
|
|
38
|
+
path_prefix: z.string().max(1024).optional(),
|
|
39
|
+
glob: z.string().max(256).optional()
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
|
|
43
|
+
export const SearchContextInputSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
project_root_path: z.string().min(1).max(1024),
|
|
46
|
+
query: z.string().min(3).max(4000),
|
|
47
|
+
top_k: z.number().int().min(1).max(20).default(8),
|
|
48
|
+
filters: SearchContextFiltersSchema.optional()
|
|
49
|
+
})
|
|
50
|
+
.strict();
|
|
51
|
+
export type SearchContextInput = z.infer<typeof SearchContextInputSchema>;
|
|
52
|
+
|
|
53
|
+
export const SearchContextResultSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
path: z.string(),
|
|
56
|
+
start_line: z.number().int().min(1),
|
|
57
|
+
end_line: z.number().int().min(1),
|
|
58
|
+
snippet: z.string(),
|
|
59
|
+
score: z.number(),
|
|
60
|
+
reason: z.string()
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
|
|
64
|
+
export const SearchContextOutputSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
trace_id: z.string().min(1),
|
|
67
|
+
results: z.array(SearchContextResultSchema),
|
|
68
|
+
search_metadata: z
|
|
69
|
+
.object({
|
|
70
|
+
latency_ms: z.number().int().min(0),
|
|
71
|
+
retrieval_mode: z.enum(["hybrid", "vector", "lexical"]),
|
|
72
|
+
index_version: z.string()
|
|
73
|
+
})
|
|
74
|
+
.strict()
|
|
75
|
+
})
|
|
76
|
+
.strict();
|
|
77
|
+
export type SearchContextOutput = z.infer<typeof SearchContextOutputSchema>;
|
|
78
|
+
|
|
79
|
+
export const SyncPushFileSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
path: z.string().min(1).max(2048),
|
|
82
|
+
content: z.string().max(1_000_000),
|
|
83
|
+
language: z.string().min(1).max(50).optional(),
|
|
84
|
+
generated: z.boolean().optional(),
|
|
85
|
+
binary: z.boolean().optional(),
|
|
86
|
+
updated_at: z.string().max(64).optional()
|
|
87
|
+
})
|
|
88
|
+
.strict();
|
|
89
|
+
export type SyncPushFile = z.infer<typeof SyncPushFileSchema>;
|
|
90
|
+
|
|
91
|
+
export const SyncPushInputSchema = z
|
|
92
|
+
.object({
|
|
93
|
+
project_root_path: z.string().min(1).max(1024),
|
|
94
|
+
workspace_id: z.string().min(1).max(128).optional(),
|
|
95
|
+
index_version: z.string().min(1).max(128).optional(),
|
|
96
|
+
files: z.array(SyncPushFileSchema).min(1).max(10_000)
|
|
97
|
+
})
|
|
98
|
+
.strict();
|
|
99
|
+
export type SyncPushInput = z.infer<typeof SyncPushInputSchema>;
|
|
100
|
+
|
|
101
|
+
export const SyncPushDeltaInputSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
project_root_path: z.string().min(1).max(1024),
|
|
104
|
+
workspace_id: z.string().min(1).max(128).optional(),
|
|
105
|
+
base_index_version: z.string().min(1).max(128).optional(),
|
|
106
|
+
upsert_files: z.array(SyncPushFileSchema).max(10_000).default([]),
|
|
107
|
+
deleted_paths: z.array(z.string().min(1).max(2048)).max(10_000).default([])
|
|
108
|
+
})
|
|
109
|
+
.strict()
|
|
110
|
+
.superRefine((value, ctx) => {
|
|
111
|
+
if (value.upsert_files.length + value.deleted_paths.length <= 0) {
|
|
112
|
+
ctx.addIssue({
|
|
113
|
+
code: z.ZodIssueCode.custom,
|
|
114
|
+
message: "at least one delta operation is required",
|
|
115
|
+
path: ["upsert_files"]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
export type SyncPushDeltaInput = z.infer<typeof SyncPushDeltaInputSchema>;
|
|
120
|
+
|
|
121
|
+
export const SyncPushReportSchema = z
|
|
122
|
+
.object({
|
|
123
|
+
status: z.string(),
|
|
124
|
+
counts: z
|
|
125
|
+
.object({
|
|
126
|
+
added: z.number().int().min(0),
|
|
127
|
+
modified: z.number().int().min(0),
|
|
128
|
+
deleted: z.number().int().min(0),
|
|
129
|
+
unchanged: z.number().int().min(0),
|
|
130
|
+
skipped: z.number().int().min(0)
|
|
131
|
+
})
|
|
132
|
+
.strict()
|
|
133
|
+
})
|
|
134
|
+
.strict();
|
|
135
|
+
export type SyncPushReport = z.infer<typeof SyncPushReportSchema>;
|
|
136
|
+
|
|
137
|
+
export const SyncPushResponseSchema = z
|
|
138
|
+
.object({
|
|
139
|
+
trace_id: z.string().min(1),
|
|
140
|
+
tenant_id: z.string().min(1),
|
|
141
|
+
workspace_id: z.string().min(1).max(128),
|
|
142
|
+
project_root_path: z.string().min(1).max(1024),
|
|
143
|
+
index_version: z.string().min(1).max(128),
|
|
144
|
+
workspace_created: z.boolean(),
|
|
145
|
+
report: SyncPushReportSchema
|
|
146
|
+
})
|
|
147
|
+
.strict();
|
|
148
|
+
export type SyncPushResponse = z.infer<typeof SyncPushResponseSchema>;
|
|
149
|
+
|
|
150
|
+
export const SyncPushDeltaResponseSchema = SyncPushResponseSchema.extend({
|
|
151
|
+
base_index_version: z.string().min(1).max(128).nullable(),
|
|
152
|
+
applied_delta: z
|
|
153
|
+
.object({
|
|
154
|
+
upsert_files: z.number().int().min(0),
|
|
155
|
+
deleted_paths: z.number().int().min(0)
|
|
156
|
+
})
|
|
157
|
+
.strict()
|
|
158
|
+
}).strict();
|
|
159
|
+
export type SyncPushDeltaResponse = z.infer<typeof SyncPushDeltaResponseSchema>;
|
|
160
|
+
|
|
161
|
+
export const ConversationMessageSchema = z
|
|
162
|
+
.object({
|
|
163
|
+
role: z.enum(["user", "assistant", "system"]),
|
|
164
|
+
content: z.string().max(4000)
|
|
165
|
+
})
|
|
166
|
+
.strict();
|
|
167
|
+
|
|
168
|
+
export const EnhancePromptInputSchema = z
|
|
169
|
+
.object({
|
|
170
|
+
prompt: z.string().min(3).max(12000),
|
|
171
|
+
conversation_history: z.array(ConversationMessageSchema).max(30),
|
|
172
|
+
project_root_path: z.string().max(1024).optional()
|
|
173
|
+
})
|
|
174
|
+
.strict();
|
|
175
|
+
export type EnhancePromptInput = z.infer<typeof EnhancePromptInputSchema>;
|
|
176
|
+
|
|
177
|
+
export const ContextRefSchema = z
|
|
178
|
+
.object({
|
|
179
|
+
path: z.string(),
|
|
180
|
+
start_line: z.number().int().min(1),
|
|
181
|
+
end_line: z.number().int().min(1),
|
|
182
|
+
reason: z.string()
|
|
183
|
+
})
|
|
184
|
+
.strict();
|
|
185
|
+
|
|
186
|
+
export const EnhancePromptOutputSchema = z
|
|
187
|
+
.object({
|
|
188
|
+
trace_id: z.string().min(1),
|
|
189
|
+
enhanced_prompt: z.string(),
|
|
190
|
+
context_refs: z.array(ContextRefSchema),
|
|
191
|
+
warnings: z.array(z.string()),
|
|
192
|
+
questions: z.array(z.string())
|
|
193
|
+
})
|
|
194
|
+
.strict();
|
|
195
|
+
export type EnhancePromptOutput = z.infer<typeof EnhancePromptOutputSchema>;
|
|
196
|
+
|
|
197
|
+
export const RetrievalBenchmarkRepoSchema = z
|
|
198
|
+
.object({
|
|
199
|
+
id: z.string().min(1).max(100),
|
|
200
|
+
git_url: z.string().min(1).max(2000),
|
|
201
|
+
commit: z.string().regex(/^[0-9a-f]{7,40}$/i),
|
|
202
|
+
split: z.enum(["train", "dev", "test"]),
|
|
203
|
+
language: z.string().max(50).optional(),
|
|
204
|
+
notes: z.string().max(500).optional()
|
|
205
|
+
})
|
|
206
|
+
.strict();
|
|
207
|
+
export type RetrievalBenchmarkRepo = z.infer<typeof RetrievalBenchmarkRepoSchema>;
|
|
208
|
+
|
|
209
|
+
export const RetrievalBenchmarkManifestSchema = z
|
|
210
|
+
.object({
|
|
211
|
+
schema_version: z.literal("v1"),
|
|
212
|
+
generated_at: z.string().datetime().optional(),
|
|
213
|
+
repos: z.array(RetrievalBenchmarkRepoSchema).min(1)
|
|
214
|
+
})
|
|
215
|
+
.strict();
|
|
216
|
+
export type RetrievalBenchmarkManifest = z.infer<typeof RetrievalBenchmarkManifestSchema>;
|
|
217
|
+
|
|
218
|
+
export const RetrievalBenchmarkQuerySchema = z
|
|
219
|
+
.object({
|
|
220
|
+
id: z.string().min(1).max(120),
|
|
221
|
+
repo_id: z.string().min(1).max(100),
|
|
222
|
+
tool: z.enum(["search_context", "enhance_prompt"]).default("search_context"),
|
|
223
|
+
query: z.string().min(3).max(4000),
|
|
224
|
+
expected_paths: z.array(z.string().min(1).max(2048)).min(1),
|
|
225
|
+
tags: z.array(z.string().min(1).max(50)).max(12).optional(),
|
|
226
|
+
notes: z.string().max(500).optional()
|
|
227
|
+
})
|
|
228
|
+
.strict();
|
|
229
|
+
export type RetrievalBenchmarkQuery = z.infer<typeof RetrievalBenchmarkQuerySchema>;
|
|
230
|
+
|
|
231
|
+
export function isPublicToolName(value: string): value is PublicToolName {
|
|
232
|
+
return PublicToolNameSchema.safeParse(value).success;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function toValidationDetails(error: z.ZodError): Record<string, unknown> {
|
|
236
|
+
const issue = error.issues[0];
|
|
237
|
+
return {
|
|
238
|
+
field: issue?.path.join(".") ?? "unknown",
|
|
239
|
+
reason: issue?.message ?? "invalid input"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function makeErrorEnvelope(input: {
|
|
244
|
+
code: ErrorCode;
|
|
245
|
+
message: string;
|
|
246
|
+
trace_id: string;
|
|
247
|
+
retryable?: boolean;
|
|
248
|
+
details?: Record<string, unknown>;
|
|
249
|
+
}): ErrorEnvelope {
|
|
250
|
+
return {
|
|
251
|
+
error: {
|
|
252
|
+
code: input.code,
|
|
253
|
+
message: input.message,
|
|
254
|
+
retryable: input.retryable ?? false,
|
|
255
|
+
trace_id: input.trace_id,
|
|
256
|
+
...(input.details ? { details: input.details } : {})
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function errorCodeToHttpStatus(code: ErrorCode): number {
|
|
262
|
+
switch (code) {
|
|
263
|
+
case "INVALID_ARGUMENT":
|
|
264
|
+
return 400;
|
|
265
|
+
case "UNAUTHENTICATED":
|
|
266
|
+
return 401;
|
|
267
|
+
case "FORBIDDEN":
|
|
268
|
+
return 403;
|
|
269
|
+
case "NOT_FOUND":
|
|
270
|
+
return 404;
|
|
271
|
+
case "CONFLICT":
|
|
272
|
+
return 409;
|
|
273
|
+
case "RATE_LIMITED":
|
|
274
|
+
return 429;
|
|
275
|
+
case "UPSTREAM_FAILURE":
|
|
276
|
+
return 503;
|
|
277
|
+
case "INTERNAL":
|
|
278
|
+
default:
|
|
279
|
+
return 500;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { RetrievalBenchmarkManifestSchema, RetrievalBenchmarkQuerySchema } from "../src/index.js";
|
|
5
|
+
|
|
6
|
+
function loadJson(path: string): unknown {
|
|
7
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function loadJsonl(path: string): unknown[] {
|
|
11
|
+
return readFileSync(path, "utf8")
|
|
12
|
+
.split(/\r?\n/)
|
|
13
|
+
.map((line) => line.trim())
|
|
14
|
+
.filter((line) => line.length > 0)
|
|
15
|
+
.map((line) => JSON.parse(line));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("benchmark assets", () => {
|
|
19
|
+
const root = process.cwd();
|
|
20
|
+
const benchmarkDir = join(root, "benchmarks");
|
|
21
|
+
|
|
22
|
+
it("validates benchmark manifest schema", () => {
|
|
23
|
+
const manifest = RetrievalBenchmarkManifestSchema.parse(loadJson(join(benchmarkDir, "repos.manifest.json")));
|
|
24
|
+
expect(manifest.repos.length).toBeGreaterThanOrEqual(3);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("validates benchmark query files and repo references", () => {
|
|
28
|
+
const manifest = RetrievalBenchmarkManifestSchema.parse(loadJson(join(benchmarkDir, "repos.manifest.json")));
|
|
29
|
+
const repoIds = new Set(manifest.repos.map((repo) => repo.id));
|
|
30
|
+
|
|
31
|
+
const splits = ["train", "dev", "test"] as const;
|
|
32
|
+
const allQueries = splits.flatMap((split) => {
|
|
33
|
+
const rows = loadJsonl(join(benchmarkDir, `queries.${split}.jsonl`));
|
|
34
|
+
return rows.map((row) => RetrievalBenchmarkQuerySchema.parse(row));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const seen = new Set<string>();
|
|
38
|
+
for (const query of allQueries) {
|
|
39
|
+
expect(repoIds.has(query.repo_id)).toBe(true);
|
|
40
|
+
expect(seen.has(query.id)).toBe(false);
|
|
41
|
+
seen.add(query.id);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
EnhancePromptInputSchema,
|
|
4
|
+
ErrorEnvelopeSchema,
|
|
5
|
+
RetrievalBenchmarkManifestSchema,
|
|
6
|
+
RetrievalBenchmarkQuerySchema,
|
|
7
|
+
SearchContextInputSchema,
|
|
8
|
+
SearchContextOutputSchema,
|
|
9
|
+
makeErrorEnvelope
|
|
10
|
+
} from "../src/index.js";
|
|
11
|
+
|
|
12
|
+
describe("contracts", () => {
|
|
13
|
+
it("applies search_context defaults", () => {
|
|
14
|
+
const parsed = SearchContextInputSchema.parse({
|
|
15
|
+
project_root_path: "/workspace/default",
|
|
16
|
+
query: "find auth guard"
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(parsed.top_k).toBe(8);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("rejects unknown search_context fields", () => {
|
|
23
|
+
const result = SearchContextInputSchema.safeParse({
|
|
24
|
+
project_root_path: "/workspace/default",
|
|
25
|
+
query: "find auth guard",
|
|
26
|
+
unexpected: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(result.success).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("validates error envelope shape", () => {
|
|
33
|
+
const envelope = makeErrorEnvelope({
|
|
34
|
+
code: "INVALID_ARGUMENT",
|
|
35
|
+
message: "top_k must be between 1 and 20",
|
|
36
|
+
trace_id: "trc_123",
|
|
37
|
+
retryable: false,
|
|
38
|
+
details: { field: "top_k" }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(ErrorEnvelopeSchema.parse(envelope)).toEqual(envelope);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("validates enhance_prompt schema", () => {
|
|
45
|
+
const parsed = EnhancePromptInputSchema.parse({
|
|
46
|
+
prompt: "Improve search ranking",
|
|
47
|
+
conversation_history: [{ role: "user", content: "Need better search" }]
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(parsed.prompt).toContain("Improve");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("validates search_context output schema", () => {
|
|
54
|
+
const output = {
|
|
55
|
+
trace_id: "trc_out",
|
|
56
|
+
results: [
|
|
57
|
+
{
|
|
58
|
+
path: "src/app.ts",
|
|
59
|
+
start_line: 1,
|
|
60
|
+
end_line: 2,
|
|
61
|
+
snippet: "const a = 1;",
|
|
62
|
+
score: 0.9,
|
|
63
|
+
reason: "semantic match"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
search_metadata: {
|
|
67
|
+
latency_ms: 7,
|
|
68
|
+
retrieval_mode: "hybrid",
|
|
69
|
+
index_version: "idx-1"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(SearchContextOutputSchema.parse(output)).toEqual(output);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("validates retrieval benchmark manifest schema", () => {
|
|
77
|
+
const manifest = RetrievalBenchmarkManifestSchema.parse({
|
|
78
|
+
schema_version: "v1",
|
|
79
|
+
repos: [
|
|
80
|
+
{
|
|
81
|
+
id: "express",
|
|
82
|
+
git_url: "https://github.com/expressjs/express.git",
|
|
83
|
+
commit: "66404b347a1690c0b783f47a8cf04a37caea8de5",
|
|
84
|
+
split: "test"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
expect(manifest.repos).toHaveLength(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("validates retrieval benchmark query schema", () => {
|
|
92
|
+
const query = RetrievalBenchmarkQuerySchema.parse({
|
|
93
|
+
id: "express-app-use",
|
|
94
|
+
repo_id: "express",
|
|
95
|
+
query: "where is app.use implemented",
|
|
96
|
+
expected_paths: ["lib/application.js"]
|
|
97
|
+
});
|
|
98
|
+
expect(query.tool).toBe("search_context");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"composite": true,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": false,
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts"]
|
|
12
|
+
}
|
package/tsconfig.json
ADDED