@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/plugin.js +23 -117
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snack-ai/opencode",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Fail-open OpenCode metadata capture for SNACK",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
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 || !isSpoolEvent(event)) {
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
- /** @param {Record<string, unknown>} event */
367
- function isSpoolEvent(event) {
368
- const allowed = new Set([
369
- "schema_version",
370
- "event_id",
371
- "installation_id",
372
- "event_type",
373
- "source_prompt_id",
374
- "source_session_id",
375
- "revision",
376
- "revision_domain",
377
- "parser_version",
378
- "occurred_at",
379
- "provider",
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
- record !== null &&
427
- Object.keys(record).every((key) =>
428
- [
429
- "analyzer_version",
430
- "estimated_input_tokens",
431
- "line_count_bucket",
432
- "code_block_count_bucket",
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 boundedString(value, maximum) {
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
- }