@danceiny/gotry 0.0.1-rc.20 → 0.0.1-rc.21
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/README.md +14 -3
- package/README.zh-CN.md +15 -4
- package/bin/gotry-bootstrap.js +53 -8
- package/cordis.gotry-patch.yml +12 -5
- package/dist/capabilities/doctor.js +88 -16
- package/dist/capabilities/session/health-watch.js +10 -3
- package/dist/scripts/benchmark-environment-bridge-e2e.js +53 -15
- package/dist/scripts/benchmark-environment-bridge-tests.js +450 -28
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +211 -2
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +132 -2
- package/dist/scripts/bootstrap-tests.js +2 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +9 -1
- package/dist/scripts/map-tools-vendor-package-proof.js +276 -0
- package/dist/scripts/persona-surface-guard-tests.js +8 -3
- package/dist/src/benchmark-agent-conformance.js +155 -5
- package/dist/src/benchmark-environment-bridge.js +53 -71
- package/dist/src/booking-surface/dsh-planner.js +24 -8
- package/extension/manifest.json +2 -2
- package/package.json +2 -1
- package/ts/capabilities/session/health-watch.ts +9 -2
- package/ts/package.json +15 -7
- package/ts/scripts/map-tools-vendor-package-proof.ts +268 -0
- package/ts/src/benchmark-agent-conformance.ts +135 -4
- package/ts/src/benchmark-environment-bridge.ts +25 -40
- package/ts/src/booking-surface/dsh-planner.ts +25 -13
|
@@ -4,9 +4,9 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { Context } from '@deepseek-ai/cordis';
|
|
6
6
|
import { apply } from '../src/index.js';
|
|
7
|
-
import { BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, registerBenchmarkEnvironmentBridge } from '../src/benchmark-environment-bridge.js';
|
|
7
|
+
import { BRIDGE_ERROR_CONTRACT, BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, registerBenchmarkEnvironmentBridge } from '../src/benchmark-environment-bridge.js';
|
|
8
8
|
import { installBenchmarkToolIsolation } from '../src/benchmark-tool-isolation.js';
|
|
9
|
-
import { BENCHMARK_BRIDGE_CALL_FAILED, BENCHMARK_BRIDGE_CALL_REQUIRED, BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED, BENCHMARK_BRIDGE_OUTPUT_TRUNCATED, BENCHMARK_BRIDGE_RUNNER_FAILED, BENCHMARK_BRIDGE_SPAWN_FAILED, BENCHMARK_BRIDGE_TIMED_OUT, BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE, BENCHMARK_TERMINAL_INVALID, MAX_CONFORMANCE_RETRIES, benchmarkChildFailureForConformanceCode, createBenchmarkAgentConformance, installBenchmarkAgentConformance, parseBenchmarkTerminal, validateTerminalOutputConfig } from '../src/benchmark-agent-conformance.js';
|
|
9
|
+
import { BENCHMARK_BRIDGE_CALL_FAILED, BENCHMARK_BRIDGE_CALL_REQUIRED, BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED, BENCHMARK_BRIDGE_OUTPUT_TRUNCATED, BENCHMARK_BRIDGE_RUNNER_FAILED, BENCHMARK_BRIDGE_SPAWN_FAILED, BENCHMARK_BRIDGE_TIMED_OUT, BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE, BENCHMARK_TERMINAL_INVALID, MAX_CONFORMANCE_RETRIES, benchmarkChildFailureForConformanceCode, createBenchmarkAgentConformance, installBenchmarkAgentConformance, parseBenchmarkTerminal, terminalSchemaOutline, validateTerminalBodySchema, validateTerminalBodyValue, validateTerminalOutputConfig } from '../src/benchmark-agent-conformance.js';
|
|
10
10
|
import { BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA, appendBoundedChildDiagnostic, classifyBenchmarkChildFailure, classifyBenchmarkTurnEnd, createBenchmarkDiagnosticArbiter, parseBenchmarkChildDiagnostic } from '../src/benchmark-headless-child-diagnostics.js';
|
|
11
11
|
{
|
|
12
12
|
const exactFamilies = [
|
|
@@ -262,6 +262,22 @@ import { BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA
|
|
|
262
262
|
'child_model_server'
|
|
263
263
|
]);
|
|
264
264
|
}assert.equal(MAX_CONFORMANCE_RETRIES, 1);
|
|
265
|
+
const OK_BODY_SCHEMA = {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: {
|
|
268
|
+
ok: {
|
|
269
|
+
type: 'boolean'
|
|
270
|
+
},
|
|
271
|
+
status: {
|
|
272
|
+
type: 'string'
|
|
273
|
+
},
|
|
274
|
+
x: {
|
|
275
|
+
type: 'string'
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
required: [],
|
|
279
|
+
additionalProperties: false
|
|
280
|
+
};
|
|
265
281
|
const projection = {
|
|
266
282
|
toolName: 'gotry_benchmark_environment',
|
|
267
283
|
allowedTools: [
|
|
@@ -269,7 +285,8 @@ const projection = {
|
|
|
269
285
|
],
|
|
270
286
|
terminal: {
|
|
271
287
|
tag: 'done',
|
|
272
|
-
max_bytes: 1024
|
|
288
|
+
max_bytes: 1024,
|
|
289
|
+
body_schema: OK_BODY_SCHEMA
|
|
273
290
|
}
|
|
274
291
|
};
|
|
275
292
|
assert.equal(validateTerminalOutputConfig(projection.terminal), true);
|
|
@@ -294,6 +311,20 @@ for (const invalid of [
|
|
|
294
311
|
tag: 'done',
|
|
295
312
|
max_bytes: 1024,
|
|
296
313
|
extra: true
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
tag: 'done',
|
|
317
|
+
max_bytes: 1024
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
tag: 'done',
|
|
321
|
+
max_bytes: 1024,
|
|
322
|
+
body_schema: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {},
|
|
325
|
+
required: [],
|
|
326
|
+
additionalProperties: true
|
|
327
|
+
}
|
|
297
328
|
}
|
|
298
329
|
])assert.equal(validateTerminalOutputConfig(invalid), false);
|
|
299
330
|
assert.deepEqual(parseBenchmarkTerminal(' \n<done>{"ok":true}</done>\n', projection.terminal), {
|
|
@@ -338,7 +369,284 @@ for (const [raw, expectOk] of [
|
|
|
338
369
|
]){
|
|
339
370
|
assert.equal(parseBenchmarkTerminal(raw, projection.terminal).ok, expectOk, raw);
|
|
340
371
|
}
|
|
341
|
-
|
|
372
|
+
{
|
|
373
|
+
const travelSchema = {
|
|
374
|
+
type: 'object',
|
|
375
|
+
properties: {
|
|
376
|
+
people_number: {
|
|
377
|
+
type: 'integer'
|
|
378
|
+
},
|
|
379
|
+
start_city: {
|
|
380
|
+
type: 'string'
|
|
381
|
+
},
|
|
382
|
+
target_city: {
|
|
383
|
+
type: 'string'
|
|
384
|
+
},
|
|
385
|
+
itinerary: {
|
|
386
|
+
type: 'array',
|
|
387
|
+
items: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
day: {
|
|
391
|
+
type: 'integer'
|
|
392
|
+
},
|
|
393
|
+
activities: {
|
|
394
|
+
type: 'array',
|
|
395
|
+
items: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
properties: {
|
|
398
|
+
type: {
|
|
399
|
+
type: 'string'
|
|
400
|
+
},
|
|
401
|
+
start_time: {
|
|
402
|
+
type: 'string'
|
|
403
|
+
},
|
|
404
|
+
cost: {
|
|
405
|
+
type: 'number'
|
|
406
|
+
},
|
|
407
|
+
transports: {
|
|
408
|
+
type: 'array',
|
|
409
|
+
items: {
|
|
410
|
+
type: 'object',
|
|
411
|
+
properties: {
|
|
412
|
+
start: {
|
|
413
|
+
type: 'string'
|
|
414
|
+
},
|
|
415
|
+
mode: {
|
|
416
|
+
type: 'string'
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
required: [
|
|
420
|
+
'start',
|
|
421
|
+
'mode'
|
|
422
|
+
],
|
|
423
|
+
additionalProperties: false
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
required: [
|
|
428
|
+
'type',
|
|
429
|
+
'start_time',
|
|
430
|
+
'cost',
|
|
431
|
+
'transports'
|
|
432
|
+
],
|
|
433
|
+
additionalProperties: false
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
required: [
|
|
438
|
+
'day',
|
|
439
|
+
'activities'
|
|
440
|
+
],
|
|
441
|
+
additionalProperties: false
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
required: [
|
|
446
|
+
'people_number',
|
|
447
|
+
'start_city',
|
|
448
|
+
'target_city',
|
|
449
|
+
'itinerary'
|
|
450
|
+
],
|
|
451
|
+
additionalProperties: false
|
|
452
|
+
};
|
|
453
|
+
const travelConfig = {
|
|
454
|
+
tag: 'done',
|
|
455
|
+
max_bytes: 65_536,
|
|
456
|
+
body_schema: travelSchema
|
|
457
|
+
};
|
|
458
|
+
assert.equal(validateTerminalOutputConfig(travelConfig), true, 'ChinaTravel-like closed schema is a valid terminal config');
|
|
459
|
+
const legalBody = {
|
|
460
|
+
people_number: 2,
|
|
461
|
+
start_city: 'Dubai',
|
|
462
|
+
target_city: 'Chengdu',
|
|
463
|
+
itinerary: [
|
|
464
|
+
{
|
|
465
|
+
day: 1,
|
|
466
|
+
activities: [
|
|
467
|
+
{
|
|
468
|
+
type: 'attraction',
|
|
469
|
+
start_time: '09:00',
|
|
470
|
+
cost: 0,
|
|
471
|
+
transports: [
|
|
472
|
+
{
|
|
473
|
+
start: 'hotel',
|
|
474
|
+
mode: 'walk'
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
assert.equal(validateTerminalBodySchema(travelSchema), true);
|
|
483
|
+
const mutate = (fn)=>{
|
|
484
|
+
const copy = JSON.parse(JSON.stringify(travelSchema));
|
|
485
|
+
fn(copy);
|
|
486
|
+
return copy;
|
|
487
|
+
};
|
|
488
|
+
for (const [label, schema] of [
|
|
489
|
+
[
|
|
490
|
+
'extra root key',
|
|
491
|
+
mutate((s)=>{
|
|
492
|
+
s.description = 'the plan';
|
|
493
|
+
})
|
|
494
|
+
],
|
|
495
|
+
[
|
|
496
|
+
'enum on scalar',
|
|
497
|
+
mutate((s)=>{
|
|
498
|
+
s.properties.people_number.enum = [
|
|
499
|
+
2
|
|
500
|
+
];
|
|
501
|
+
})
|
|
502
|
+
],
|
|
503
|
+
[
|
|
504
|
+
'const on scalar',
|
|
505
|
+
mutate((s)=>{
|
|
506
|
+
s.properties.start_city.const = 'Dubai';
|
|
507
|
+
})
|
|
508
|
+
],
|
|
509
|
+
[
|
|
510
|
+
'default on scalar',
|
|
511
|
+
mutate((s)=>{
|
|
512
|
+
s.properties.target_city.default = 'Chengdu';
|
|
513
|
+
})
|
|
514
|
+
],
|
|
515
|
+
[
|
|
516
|
+
'examples key',
|
|
517
|
+
mutate((s)=>{
|
|
518
|
+
s.properties.itinerary.examples = [];
|
|
519
|
+
})
|
|
520
|
+
],
|
|
521
|
+
[
|
|
522
|
+
'pattern on scalar',
|
|
523
|
+
mutate((s)=>{
|
|
524
|
+
s.properties.start_city.pattern = '^[A-Z]';
|
|
525
|
+
})
|
|
526
|
+
],
|
|
527
|
+
[
|
|
528
|
+
'open object',
|
|
529
|
+
mutate((s)=>{
|
|
530
|
+
s.additionalProperties = true;
|
|
531
|
+
})
|
|
532
|
+
],
|
|
533
|
+
[
|
|
534
|
+
'missing additionalProperties',
|
|
535
|
+
mutate((s)=>{
|
|
536
|
+
delete s.additionalProperties;
|
|
537
|
+
})
|
|
538
|
+
],
|
|
539
|
+
[
|
|
540
|
+
'array without items',
|
|
541
|
+
mutate((s)=>{
|
|
542
|
+
s.properties.itinerary = {
|
|
543
|
+
type: 'array'
|
|
544
|
+
};
|
|
545
|
+
})
|
|
546
|
+
],
|
|
547
|
+
[
|
|
548
|
+
'object without required',
|
|
549
|
+
mutate((s)=>{
|
|
550
|
+
delete s.required;
|
|
551
|
+
})
|
|
552
|
+
],
|
|
553
|
+
[
|
|
554
|
+
'unknown type',
|
|
555
|
+
mutate((s)=>{
|
|
556
|
+
s.properties.people_number.type = 'float';
|
|
557
|
+
})
|
|
558
|
+
],
|
|
559
|
+
[
|
|
560
|
+
'allOf composition',
|
|
561
|
+
mutate((s)=>{
|
|
562
|
+
s.allOf = [];
|
|
563
|
+
})
|
|
564
|
+
]
|
|
565
|
+
]){
|
|
566
|
+
assert.equal(validateTerminalBodySchema(schema), false, `schema dialect rejects: ${label}`);
|
|
567
|
+
}
|
|
568
|
+
assert.equal(validateTerminalBodyValue(legalBody, travelSchema), true, 'legal ChinaTravel-like hierarchy passes');
|
|
569
|
+
assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(legalBody)}</done>`, travelConfig).ok, true, 'legal body parses through the terminal gate');
|
|
570
|
+
for (const [label, body] of [
|
|
571
|
+
[
|
|
572
|
+
'extra root key (Round 11 budget/total_cost case)',
|
|
573
|
+
{
|
|
574
|
+
...legalBody,
|
|
575
|
+
budget: {
|
|
576
|
+
total_cost: 1
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
],
|
|
580
|
+
[
|
|
581
|
+
'itinerary item written as direct activity (missing day)',
|
|
582
|
+
{
|
|
583
|
+
...legalBody,
|
|
584
|
+
itinerary: [
|
|
585
|
+
{
|
|
586
|
+
type: 'attraction',
|
|
587
|
+
start_time: '09:00',
|
|
588
|
+
cost: 0,
|
|
589
|
+
transports: []
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
[
|
|
595
|
+
'missing activities array',
|
|
596
|
+
{
|
|
597
|
+
...legalBody,
|
|
598
|
+
itinerary: [
|
|
599
|
+
{
|
|
600
|
+
day: 1
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
[
|
|
606
|
+
'wrong type (string day)',
|
|
607
|
+
{
|
|
608
|
+
...legalBody,
|
|
609
|
+
itinerary: [
|
|
610
|
+
{
|
|
611
|
+
day: '1',
|
|
612
|
+
activities: []
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
}
|
|
616
|
+
],
|
|
617
|
+
[
|
|
618
|
+
'nested extra field',
|
|
619
|
+
{
|
|
620
|
+
...legalBody,
|
|
621
|
+
itinerary: [
|
|
622
|
+
{
|
|
623
|
+
day: 1,
|
|
624
|
+
activities: [
|
|
625
|
+
{
|
|
626
|
+
type: 'attraction',
|
|
627
|
+
start_time: '09:00',
|
|
628
|
+
cost: 0,
|
|
629
|
+
transports: [],
|
|
630
|
+
unexpected: 1
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
]
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
]){
|
|
638
|
+
assert.equal(validateTerminalBodyValue(body, travelSchema), false, `value validation rejects: ${label}`);
|
|
639
|
+
assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(body)}</done>`, travelConfig).ok, false, `terminal gate rejects: ${label}`);
|
|
640
|
+
}
|
|
641
|
+
assert.equal(parseBenchmarkTerminal(`<done>${' '.repeat(2000)}${JSON.stringify({
|
|
642
|
+
ok: true
|
|
643
|
+
})}</done>`, projection.terminal).ok, false, 'schema-valid body over max_bytes still rejects');
|
|
644
|
+
const outline = terminalSchemaOutline(travelSchema);
|
|
645
|
+
assert.equal(outline, terminalSchemaOutline(JSON.parse(JSON.stringify(travelSchema))), 'outline is deterministic');
|
|
646
|
+
assert.equal(outline, 'object{people_number:integer,start_city:string,target_city:string,itinerary:array<object{day:integer,activities:array<object{type:string,start_time:string,cost:number,transports:array<object{start:string,mode:string}>}>}>}', 'outline renders the exact structural contract');
|
|
647
|
+
assert.match(outline, /object\{/, 'outline renders object nodes');
|
|
648
|
+
assert.ok(!outline.includes('optional') && !outline.includes('?'), 'all keys required render without optional markers');
|
|
649
|
+
}function turnStart(turn = 1) {
|
|
342
650
|
return {
|
|
343
651
|
type: 'turn/start',
|
|
344
652
|
data: {
|
|
@@ -874,6 +1182,8 @@ function toolResult(callId = 'call-1', options = {}) {
|
|
|
874
1182
|
assert.match(assembled.sections[0].text, /\"action\":\"call\"/);
|
|
875
1183
|
assert.match(assembled.sections[0].text, /<done>/);
|
|
876
1184
|
assert.equal(assembled.sections[0].text.includes('/tmp/'), false);
|
|
1185
|
+
const round12Outline = terminalSchemaOutline(OK_BODY_SCHEMA);
|
|
1186
|
+
assert.ok(assembled.sections[0].text.includes(`matching exactly ${round12Outline}`), 'system section projects the terminal body schema outline');
|
|
877
1187
|
rootListeners.get('session/event')[0](session, turnEnd());
|
|
878
1188
|
assert.deepEqual(runtimeWrites, []);
|
|
879
1189
|
rootListeners.get('session/event')[0](session, turnStart(2));
|
|
@@ -1206,7 +1516,7 @@ try {
|
|
|
1206
1516
|
assert.equal(parseBenchmarkChildDiagnostic(boundedControl.toString('utf8')), 'child_bridge_timed_out', 'rolling tail preserves a final structured reason after bounded noise');
|
|
1207
1517
|
const configPath = join(root, 'bridge.json');
|
|
1208
1518
|
writeFileSync(configPath, JSON.stringify({
|
|
1209
|
-
schema_version: '
|
|
1519
|
+
schema_version: 'gotry_benchmark_environment_bridge_v4',
|
|
1210
1520
|
enabled: true,
|
|
1211
1521
|
executable: process.execPath,
|
|
1212
1522
|
cwd: root,
|
|
@@ -1268,7 +1578,8 @@ try {
|
|
|
1268
1578
|
max_output_bytes: 4_096,
|
|
1269
1579
|
terminal_output: {
|
|
1270
1580
|
tag: 'done',
|
|
1271
|
-
max_bytes: 4_096
|
|
1581
|
+
max_bytes: 4_096,
|
|
1582
|
+
body_schema: OK_BODY_SCHEMA
|
|
1272
1583
|
},
|
|
1273
1584
|
isolation: {
|
|
1274
1585
|
mode: 'host-enforced',
|
|
@@ -1891,29 +2202,28 @@ try {
|
|
|
1891
2202
|
const bridge = registered.find((tool)=>tool.name === 'gotry_benchmark_environment');
|
|
1892
2203
|
assert.ok(bridge.execute, 'registered bridge exposes execute');
|
|
1893
2204
|
const parameters = bridge.parameters;
|
|
1894
|
-
assert.equal(
|
|
1895
|
-
|
|
1896
|
-
assert.
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
'
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
assert.deepEqual(lookupBranch.properties.arguments.required, [
|
|
1905
|
-
'city'
|
|
1906
|
-
]);
|
|
1907
|
-
assert.equal(lookupBranch.properties.arguments.additionalProperties, false);
|
|
1908
|
-
assert.equal(lookupBranch.properties.arguments.properties.city.type, 'string');
|
|
1909
|
-
assert.deepEqual(lookupBranch.properties.arguments.properties.city.enum, [
|
|
1910
|
-
'Dubai',
|
|
1911
|
-
'Abu Dhabi'
|
|
2205
|
+
assert.equal(parameters.type, 'object', 'flat bridge uses an object root for model-facing parameters');
|
|
2206
|
+
assert.equal(parameters.oneOf, undefined, 'model-facing wire has no top-level oneOf');
|
|
2207
|
+
assert.deepEqual(parameters.required, [
|
|
2208
|
+
'action'
|
|
2209
|
+
], 'action is the only universally required wire field');
|
|
2210
|
+
assert.equal(parameters.additionalProperties, false);
|
|
2211
|
+
assert.deepEqual(parameters.properties?.action?.enum, [
|
|
2212
|
+
'tools',
|
|
2213
|
+
'call',
|
|
2214
|
+
'errors'
|
|
1912
2215
|
]);
|
|
2216
|
+
assert.deepEqual(parameters.properties?.tool?.enum, [
|
|
2217
|
+
'lookup',
|
|
2218
|
+
'constructor',
|
|
2219
|
+
'toString'
|
|
2220
|
+
], 'tool enum exposes frozen descriptor names');
|
|
2221
|
+
assert.equal(parameters.properties?.arguments?.type, 'object', 'arguments remains a generic object on the open wire');
|
|
2222
|
+
assert.equal(parameters.properties?.arguments?.additionalProperties, true);
|
|
1913
2223
|
assert.equal(Object.isFrozen(bridge.parameters), true, 'registered raw parameters are frozen');
|
|
1914
|
-
assert.equal(Object.isFrozen(
|
|
2224
|
+
assert.equal(Object.isFrozen(parameters.properties?.tool?.enum), true, 'descriptor-derived tool enum is frozen');
|
|
1915
2225
|
assert.throws(()=>{
|
|
1916
|
-
|
|
2226
|
+
parameters.properties?.tool?.enum.push('escape');
|
|
1917
2227
|
}, TypeError);
|
|
1918
2228
|
const beforeProtocolRejected = spawnSpecs.length;
|
|
1919
2229
|
await assert.rejects(()=>bridge.execute({
|
|
@@ -1926,15 +2236,54 @@ try {
|
|
|
1926
2236
|
action: 'tools',
|
|
1927
2237
|
tool: 'lookup'
|
|
1928
2238
|
}, null), /invalid arguments/, 'mixed action/tool fields are rejected by the flat protocol');
|
|
2239
|
+
await assert.rejects(()=>bridge.execute({
|
|
2240
|
+
action: 'tools',
|
|
2241
|
+
arguments: {}
|
|
2242
|
+
}, null), /invalid arguments/, 'tools rejects mixed arguments');
|
|
2243
|
+
await assert.rejects(()=>bridge.execute({
|
|
2244
|
+
action: 'errors',
|
|
2245
|
+
tool: 'lookup'
|
|
2246
|
+
}, null), /invalid arguments/, 'errors rejects mixed tool fields');
|
|
2247
|
+
await assert.rejects(()=>bridge.execute({
|
|
2248
|
+
action: 'errors',
|
|
2249
|
+
arguments: {}
|
|
2250
|
+
}, null), /invalid arguments/, 'errors rejects mixed arguments');
|
|
1929
2251
|
await assert.rejects(()=>bridge.execute({
|
|
1930
2252
|
...bridgeCall('lookup', {
|
|
1931
2253
|
city: 'Dubai'
|
|
1932
2254
|
}),
|
|
1933
2255
|
extra: true
|
|
1934
2256
|
}, null), /invalid arguments/, 'extra top-level fields are rejected by the flat protocol');
|
|
2257
|
+
await assert.rejects(()=>bridge.execute({
|
|
2258
|
+
action: 'call'
|
|
2259
|
+
}, null), /invalid arguments/, 'call without tool or arguments is rejected');
|
|
2260
|
+
await assert.rejects(()=>bridge.execute({
|
|
2261
|
+
action: 'call',
|
|
2262
|
+
tool: 'lookup'
|
|
2263
|
+
}, null), /invalid arguments/, 'call without arguments is rejected');
|
|
2264
|
+
await assert.rejects(()=>bridge.execute({
|
|
2265
|
+
action: 'call',
|
|
2266
|
+
arguments: {
|
|
2267
|
+
city: 'Dubai'
|
|
2268
|
+
}
|
|
2269
|
+
}, null), /invalid arguments/, 'call without tool is rejected');
|
|
1935
2270
|
await assert.rejects(()=>bridge.execute(bridgeCall('lookup', {
|
|
1936
2271
|
city: 'Sharjah'
|
|
1937
2272
|
}), null), /invalid arguments/, 'invalid descriptor argument values are rejected by the flat protocol');
|
|
2273
|
+
await assert.rejects(()=>bridge.execute({
|
|
2274
|
+
action: 'call',
|
|
2275
|
+
tool: 'lookup',
|
|
2276
|
+
arguments: {}
|
|
2277
|
+
}, null), /invalid arguments/, 'missing required descriptor arguments are rejected by the flat protocol');
|
|
2278
|
+
for (const tool of [
|
|
2279
|
+
'lookup',
|
|
2280
|
+
'constructor',
|
|
2281
|
+
'toString'
|
|
2282
|
+
]){
|
|
2283
|
+
await assert.rejects(()=>bridge.execute(bridgeCall(tool, {
|
|
2284
|
+
city: 'Sharjah'
|
|
2285
|
+
}), null), /invalid arguments/, `${tool} rejects invalid descriptor arguments`);
|
|
2286
|
+
}
|
|
1938
2287
|
assert.equal(spawnSpecs.length, beforeProtocolRejected, 'flat protocol rejections happen before spawn');
|
|
1939
2288
|
const args = {
|
|
1940
2289
|
city: 'Dubai',
|
|
@@ -2297,6 +2646,64 @@ try {
|
|
|
2297
2646
|
}
|
|
2298
2647
|
assert.equal(errorsTable.errors?.invalid_arguments?.recoverable, true, 'invalid caller arguments are recoverable');
|
|
2299
2648
|
assert.equal(errorsTable.errors?.forbidden_output?.recoverable, false, 'policy-boundary output is not recoverable');
|
|
2649
|
+
assert.deepEqual(errorsTable, {
|
|
2650
|
+
ok: true,
|
|
2651
|
+
errors: BRIDGE_ERROR_CONTRACT
|
|
2652
|
+
}, 'errors action returns the exact closed failure contract');
|
|
2653
|
+
const toolsTable = await bridge.execute({
|
|
2654
|
+
action: 'tools'
|
|
2655
|
+
}, null);
|
|
2656
|
+
assert.deepEqual(toolsTable, {
|
|
2657
|
+
ok: true,
|
|
2658
|
+
tools: [
|
|
2659
|
+
{
|
|
2660
|
+
name: 'lookup',
|
|
2661
|
+
description: 'Lookup one declared city.',
|
|
2662
|
+
input_schema: lookupInputSchema(),
|
|
2663
|
+
output_keys: [
|
|
2664
|
+
'city',
|
|
2665
|
+
'nested'
|
|
2666
|
+
],
|
|
2667
|
+
domain_outcomes: [
|
|
2668
|
+
{
|
|
2669
|
+
status: 'miss',
|
|
2670
|
+
code: 'NOT_FOUND',
|
|
2671
|
+
recovery: 'revise_arguments'
|
|
2672
|
+
},
|
|
2673
|
+
{
|
|
2674
|
+
status: 'error',
|
|
2675
|
+
code: 'AMBIGUOUS',
|
|
2676
|
+
recovery: 'choose_alternative'
|
|
2677
|
+
}
|
|
2678
|
+
]
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
name: 'constructor',
|
|
2682
|
+
description: 'Construct one declared city.',
|
|
2683
|
+
input_schema: lookupInputSchema(),
|
|
2684
|
+
output_keys: [
|
|
2685
|
+
'legacy'
|
|
2686
|
+
],
|
|
2687
|
+
domain_outcomes: [
|
|
2688
|
+
{
|
|
2689
|
+
status: 'error',
|
|
2690
|
+
code: 'INVALID',
|
|
2691
|
+
recovery: 'retry_same'
|
|
2692
|
+
}
|
|
2693
|
+
]
|
|
2694
|
+
},
|
|
2695
|
+
{
|
|
2696
|
+
name: 'toString',
|
|
2697
|
+
description: 'Stringify one declared city.',
|
|
2698
|
+
input_schema: lookupInputSchema(),
|
|
2699
|
+
output_keys: [
|
|
2700
|
+
'city',
|
|
2701
|
+
'nested'
|
|
2702
|
+
],
|
|
2703
|
+
domain_outcomes: []
|
|
2704
|
+
}
|
|
2705
|
+
]
|
|
2706
|
+
}, 'tools action returns every exact frozen descriptor, including its input schema');
|
|
2300
2707
|
for (const stdout of [
|
|
2301
2708
|
'not-json',
|
|
2302
2709
|
'{"result":1}{"result":2}'
|
|
@@ -2669,7 +3076,7 @@ try {
|
|
|
2669
3076
|
else process.env.GOTRY_LLM_MODEL = originalModelOverride;
|
|
2670
3077
|
}
|
|
2671
3078
|
const validConfig = {
|
|
2672
|
-
schema_version: '
|
|
3079
|
+
schema_version: 'gotry_benchmark_environment_bridge_v4',
|
|
2673
3080
|
enabled: true,
|
|
2674
3081
|
executable: process.execPath,
|
|
2675
3082
|
cwd: root,
|
|
@@ -2716,7 +3123,8 @@ try {
|
|
|
2716
3123
|
max_output_bytes: 4_096,
|
|
2717
3124
|
terminal_output: {
|
|
2718
3125
|
tag: 'done',
|
|
2719
|
-
max_bytes: 4_096
|
|
3126
|
+
max_bytes: 4_096,
|
|
3127
|
+
body_schema: OK_BODY_SCHEMA
|
|
2720
3128
|
},
|
|
2721
3129
|
isolation: {
|
|
2722
3130
|
mode: 'host-enforced',
|
|
@@ -2891,6 +3299,20 @@ try {
|
|
|
2891
3299
|
...validConfig,
|
|
2892
3300
|
schema_version: 'gotry_benchmark_environment_bridge_v1'
|
|
2893
3301
|
}), /benchmark environment bridge configuration unavailable/, 'v1 config cannot silently omit the Round 3 terminal semantics');
|
|
3302
|
+
assert.throws(()=>bridgeRegistrationFor({
|
|
3303
|
+
...validConfig,
|
|
3304
|
+
schema_version: 'gotry_benchmark_environment_bridge_v3'
|
|
3305
|
+
}), /benchmark environment bridge configuration unavailable/, 'v3 config cannot silently omit the Round 12 terminal body schema (#215)');
|
|
3306
|
+
assert.throws(()=>bridgeRegistrationFor({
|
|
3307
|
+
...validConfig,
|
|
3308
|
+
terminal_output: {
|
|
3309
|
+
...validConfig.terminal_output,
|
|
3310
|
+
body_schema: {
|
|
3311
|
+
...validConfig.terminal_output.body_schema,
|
|
3312
|
+
additionalProperties: true
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}), /benchmark environment bridge configuration unavailable/, 'non-closed terminal body schema fails the config load closed');
|
|
2894
3316
|
assert.throws(()=>bridgeRegistrationFor({
|
|
2895
3317
|
...validConfig,
|
|
2896
3318
|
tools: [
|