@snack-ai/opencode 1.0.2 → 1.0.3
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 +1 -1
- package/src/plugin.js +23 -117
package/package.json
CHANGED
package/src/plugin.js
CHANGED
|
@@ -39,7 +39,7 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
39
39
|
/** @param {Record<string, unknown>} event */
|
|
40
40
|
/** @param {string} targetDirectory @param {Record<string, unknown>} event */
|
|
41
41
|
const append = (targetDirectory, event) => {
|
|
42
|
-
if (!installationId || !targetDirectory || !
|
|
42
|
+
if (!installationId || !targetDirectory || !withinSchemaBounds(event)) {
|
|
43
43
|
warn();
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
@@ -363,126 +363,32 @@ function recordOrNull(value) {
|
|
|
363
363
|
: null;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
/**
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
"model",
|
|
381
|
-
"completion",
|
|
382
|
-
"outcome",
|
|
383
|
-
"usage_slices",
|
|
384
|
-
"restrictions",
|
|
385
|
-
"input_features",
|
|
386
|
-
]);
|
|
387
|
-
return (
|
|
388
|
-
Object.keys(event).every((key) => allowed.has(key)) &&
|
|
389
|
-
event.schema_version === 1 &&
|
|
390
|
-
boundedString(event.event_id, 200) &&
|
|
391
|
-
boundedString(event.installation_id, 200) &&
|
|
392
|
-
boundedString(event.source_prompt_id, 200) &&
|
|
393
|
-
boundedString(event.source_session_id, 200) &&
|
|
394
|
-
boundedString(event.revision, 200) &&
|
|
395
|
-
timestamp(event.occurred_at) &&
|
|
396
|
-
nullableBoundedString(event.provider, 100) &&
|
|
397
|
-
nullableBoundedString(event.model, 200) &&
|
|
398
|
-
Array.isArray(event.usage_slices) &&
|
|
399
|
-
event.usage_slices.length === 0 &&
|
|
400
|
-
Array.isArray(event.restrictions) &&
|
|
401
|
-
event.restrictions.length <= 20 &&
|
|
402
|
-
event.restrictions.every((restriction) => {
|
|
403
|
-
const record = recordOrNull(restriction);
|
|
404
|
-
return (
|
|
405
|
-
record !== null &&
|
|
406
|
-
Object.keys(record).every((key) =>
|
|
407
|
-
["class", "source_code", "observed_at", "classifier_version"].includes(key),
|
|
408
|
-
) &&
|
|
409
|
-
["rate_limit", "usage_limit", "capacity_policy", "other_explicit_limit"].includes(
|
|
410
|
-
String(record.class),
|
|
411
|
-
) &&
|
|
412
|
-
boundedString(record.source_code, 100) &&
|
|
413
|
-
timestamp(record.observed_at) &&
|
|
414
|
-
record.classifier_version === "opencode-plugin-error-v1"
|
|
415
|
-
);
|
|
416
|
-
}) &&
|
|
417
|
-
validEventState(event) &&
|
|
418
|
-
(event.input_features === undefined || isInputFeatures(event.input_features))
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/** @param {unknown} value */
|
|
423
|
-
function isInputFeatures(value) {
|
|
424
|
-
const record = recordOrNull(value);
|
|
366
|
+
/**
|
|
367
|
+
* Refuse an event whose host-supplied identifiers do not fit the published schema.
|
|
368
|
+
*
|
|
369
|
+
* This is the whole of what a writer can get wrong. Every other field of an event -- the versions,
|
|
370
|
+
* the domains, the event type and the completion/outcome/restrictions triple the schema constrains
|
|
371
|
+
* per type -- is a literal written a few lines above, so re-deriving it from the assembled object
|
|
372
|
+
* only restated the constructor. What OpenCode supplies is different: session and message ids, a
|
|
373
|
+
* provider and a model name, none of them bounded by the host. An id longer than the schema allows
|
|
374
|
+
* cannot be written, because `spool.js` validates the same schema on the way back in and would
|
|
375
|
+
* report the line as corruption rather than as a prompt that was never representable.
|
|
376
|
+
*
|
|
377
|
+
* @param {Record<string, unknown>} event
|
|
378
|
+
*/
|
|
379
|
+
function withinSchemaBounds(event) {
|
|
425
380
|
return (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
"attachment_count",
|
|
434
|
-
].includes(key),
|
|
435
|
-
) &&
|
|
436
|
-
record.analyzer_version === "opencode-input-v1" &&
|
|
437
|
-
Number.isSafeInteger(record.estimated_input_tokens) &&
|
|
438
|
-
typeof record.estimated_input_tokens === "number" &&
|
|
439
|
-
record.estimated_input_tokens >= 0 &&
|
|
440
|
-
["0", "1-10", "11-50", "51-200", "201+"].includes(String(record.line_count_bucket)) &&
|
|
441
|
-
["0", "1", "2-4", "5+"].includes(String(record.code_block_count_bucket)) &&
|
|
442
|
-
Number.isSafeInteger(record.attachment_count) &&
|
|
443
|
-
typeof record.attachment_count === "number" &&
|
|
444
|
-
record.attachment_count >= 0
|
|
381
|
+
bounded(event.event_id, 200) &&
|
|
382
|
+
bounded(event.installation_id, 200) &&
|
|
383
|
+
bounded(event.source_prompt_id, 200) &&
|
|
384
|
+
bounded(event.source_session_id, 200) &&
|
|
385
|
+
bounded(event.revision, 200) &&
|
|
386
|
+
(event.provider === null || bounded(event.provider, 100)) &&
|
|
387
|
+
(event.model === null || bounded(event.model, 200))
|
|
445
388
|
);
|
|
446
389
|
}
|
|
447
390
|
|
|
448
391
|
/** @param {unknown} value @param {number} maximum */
|
|
449
|
-
function
|
|
392
|
+
function bounded(value, maximum) {
|
|
450
393
|
return typeof value === "string" && value.length > 0 && value.length <= maximum;
|
|
451
394
|
}
|
|
452
|
-
|
|
453
|
-
/** @param {unknown} value @param {number} maximum */
|
|
454
|
-
function nullableBoundedString(value, maximum) {
|
|
455
|
-
return value === null || boundedString(value, maximum);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/** @param {unknown} value */
|
|
459
|
-
function timestamp(value) {
|
|
460
|
-
return (
|
|
461
|
-
typeof value === "string" &&
|
|
462
|
-
/^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/u.test(value) &&
|
|
463
|
-
!Number.isNaN(Date.parse(value))
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/** @param {Record<string, unknown>} event */
|
|
468
|
-
function validEventState(event) {
|
|
469
|
-
const restrictions = /** @type {unknown[]} */ (event.restrictions);
|
|
470
|
-
if (event.event_type === "prompt_started") {
|
|
471
|
-
return (
|
|
472
|
-
event.completion === "provisional" &&
|
|
473
|
-
event.outcome === "excluded" &&
|
|
474
|
-
restrictions.length === 0
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
if (event.event_type === "session_idle") {
|
|
478
|
-
return (
|
|
479
|
-
event.completion === "completed" && event.outcome === "success" && restrictions.length === 0
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
return (
|
|
483
|
-
event.event_type === "session_error" &&
|
|
484
|
-
event.completion === "completed" &&
|
|
485
|
-
((event.outcome === "restricted" && restrictions.length > 0) ||
|
|
486
|
-
(event.outcome === "excluded" && restrictions.length === 0))
|
|
487
|
-
);
|
|
488
|
-
}
|