@openwop/openwop-conformance 1.37.0 → 1.44.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/CHANGELOG.md +38 -0
- package/README.md +2 -2
- package/api/openapi.yaml +62 -5
- package/fixtures/conformance-agent-memory-injection-budget.json +44 -0
- package/fixtures/conformance-context-budget-multiturn.json +50 -0
- package/fixtures.md +2 -0
- package/package.json +1 -1
- package/schemas/README.md +5 -0
- package/schemas/a2ui-surface-delta-frame.schema.json +48 -0
- package/schemas/capabilities.schema.json +155 -1
- package/schemas/channel-presence-payload.schema.json +41 -0
- package/schemas/compact-tool-descriptor.schema.json +51 -0
- package/schemas/conversation-turn.schema.json +10 -0
- package/schemas/frontend-plugin-manifest.schema.json +93 -0
- package/schemas/memory-list-options.schema.json +16 -0
- package/schemas/run-event-payloads.schema.json +25 -2
- package/schemas/run-event.schema.json +2 -0
- package/schemas/ui-plugin-message.schema.json +90 -0
- package/src/lib/toolCatalog.ts +89 -0
- package/src/scenarios/a2ui-surface-delta-transport.test.ts +600 -0
- package/src/scenarios/channel-presence-behavioral.test.ts +83 -0
- package/src/scenarios/channel-presence-shape.test.ts +93 -0
- package/src/scenarios/context-budget-transcript-bound.test.ts +253 -0
- package/src/scenarios/context-summarization-replay.test.ts +155 -0
- package/src/scenarios/conversation-turn-model-provenance-shape.test.ts +120 -0
- package/src/scenarios/frontend-plugin-packs.test.ts +230 -0
- package/src/scenarios/memory-injection-budget.test.ts +188 -0
- package/src/scenarios/prompt-prefix-cache.test.ts +200 -0
- package/src/scenarios/run-transport-economy.test.ts +236 -0
- package/src/scenarios/tool-catalog-compact-projection.test.ts +149 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://openwop.dev/spec/v1/run-event-payloads.schema.json",
|
|
4
4
|
"title": "RunEventPayloads",
|
|
5
|
-
"description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\
|
|
5
|
+
"description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\n109 variants from `run-event.schema.json#$defs.RunEventType` are covered, grouped into ~20 shape families with shared $defs. Naming convention: camelCase keys mirror dotted RunEventType names (e.g., `run.started` → `runStarted`).",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"$defs": {
|
|
8
8
|
"_typeIndex": {
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
"interrupt.requested": { "$ref": "#/$defs/interruptRequested" },
|
|
39
39
|
"interrupt.resolved": { "$ref": "#/$defs/interruptResolved" },
|
|
40
40
|
"channel.written": { "$ref": "#/$defs/channelWritten" },
|
|
41
|
+
"channel.presence": { "$ref": "#/$defs/channelPresence" },
|
|
42
|
+
"context.summarized": { "$ref": "#/$defs/contextSummarized" },
|
|
41
43
|
"artifact.created": { "$ref": "#/$defs/artifactCreated" },
|
|
42
44
|
"output.chunk": { "$ref": "#/$defs/outputChunk" },
|
|
43
45
|
"variable.changed": { "$ref": "#/$defs/variableChanged" },
|
|
@@ -781,6 +783,25 @@
|
|
|
781
783
|
"$ref": "https://openwop.dev/spec/v1/channel-written-payload.schema.json"
|
|
782
784
|
},
|
|
783
785
|
|
|
786
|
+
"channelPresence": {
|
|
787
|
+
"$ref": "https://openwop.dev/spec/v1/channel-presence-payload.schema.json"
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
"contextSummarized": {
|
|
791
|
+
"type": "object",
|
|
792
|
+
"description": "RFC 0111. Emitted when the host replaces older in-window orchestrator transcript turns with a host-produced summary to honor `multiAgent.executionModel.contextBudget.transcriptTokenBudget`. CONTENT-FREE: the summary text is NEVER inlined — `summaryRef` is an artifactId resolved via `GET /v1/runs/{runId}/artifacts/{artifactId}`. The summary is a NONDETERMINISTIC host output governed like an RFC 0041 envelope: on `:fork mode:replay` the host MUST reuse this recorded `summaryRef` and MUST NOT re-summarize (see multi-agent-execution.md §\"Context economy (RFC 0111)\"). `replacedTurns` lists the event ids the summary stands in for, so a replay engine reconstructs the exact transcript.",
|
|
793
|
+
"required": ["iteration", "replacedTurns", "summaryRef", "tokenCounter", "tokensBefore", "tokensAfter"],
|
|
794
|
+
"properties": {
|
|
795
|
+
"iteration": { "type": "integer", "minimum": 0, "description": "The orchestrator-loop iteration (the `runOrchestrator.decided.iteration` counter) whose transcript assembly triggered this summarization." },
|
|
796
|
+
"replacedTurns": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Event ids (run event-log entries) the summary stands in for. A replay engine reconstructs the exact model-facing transcript by substituting the `summaryRef` artifact for this contiguous range." },
|
|
797
|
+
"summaryRef": { "type": "string", "minLength": 1, "description": "ArtifactId of the persisted summary (read via `GET /v1/runs/{runId}/artifacts/{artifactId}`). The summary text is NOT on the wire. MUST be persisted/readable as-of the iteration's event-log index; a host that cannot serve it at the requested `fromSeq` MUST refuse the fork (mirrors `replay_memory_snapshot_unavailable`)." },
|
|
798
|
+
"tokenCounter": { "type": "string", "enum": ["o200k_base", "cl100k_base", "chars", "host-defined"], "description": "The unit `tokensBefore`/`tokensAfter` are denominated in. MUST equal the advertised `contextBudget.tokenCounter`." },
|
|
799
|
+
"tokensBefore": { "type": "integer", "minimum": 0, "description": "Token count of the `replacedTurns` range before summarization, in `tokenCounter` units." },
|
|
800
|
+
"tokensAfter": { "type": "integer", "minimum": 0, "description": "Token count of the summary that replaced the range, in `tokenCounter` units. Expected ≤ `tokensBefore`." }
|
|
801
|
+
},
|
|
802
|
+
"additionalProperties": false
|
|
803
|
+
},
|
|
804
|
+
|
|
784
805
|
"artifactCreated": {
|
|
785
806
|
"type": "object",
|
|
786
807
|
"description": "Emitted when a node produces a typed artifact (PRD, theme, plan, etc.).",
|
|
@@ -1049,7 +1070,7 @@
|
|
|
1049
1070
|
|
|
1050
1071
|
"providerUsage": {
|
|
1051
1072
|
"type": "object",
|
|
1052
|
-
"description": "RFC 0026. Per-call usage record emitted after every LLM provider invocation. Durably persisted in the run event log; consumed by replay, webhook subscribers, billing reconciliation. The OTel `openwop.cost.*` attribute group (per `observability.md §\"Cost attribution attributes\"`) is the observability sibling — this event type is the durable record. Replay determinism: `inputTokens` + `outputTokens` MUST replay identically; `costEstimateUsd` MAY be omitted on replay. The payload MUST NOT carry credentialRefs, hashed credential identifiers, or prompt/response substrings per `SECURITY/threat-model-secret-leakage.md §SR-1` (enforced by SECURITY invariant `provider-usage-no-credential-leak`).",
|
|
1073
|
+
"description": "RFC 0026. Per-call usage record emitted after every LLM provider invocation. Durably persisted in the run event log; consumed by replay, webhook subscribers, billing reconciliation. The OTel `openwop.cost.*` attribute group (per `observability.md §\"Cost attribution attributes\"`) is the observability sibling — this event type is the durable record. Replay determinism: `inputTokens` + `outputTokens` MUST replay identically; `costEstimateUsd` MAY be omitted on replay, and so MAY the RFC 0116 cost-only `cacheReadTokens`/`cacheWriteTokens` (they are NOT replay-asserted — a prompt-prefix cache hit vs miss MUST NOT change `inputTokens`/`outputTokens` or the recorded envelope). The payload MUST NOT carry credentialRefs, hashed credential identifiers, or prompt/response substrings per `SECURITY/threat-model-secret-leakage.md §SR-1` (enforced by SECURITY invariant `provider-usage-no-credential-leak`).",
|
|
1053
1074
|
"required": ["provider", "model", "inputTokens", "outputTokens"],
|
|
1054
1075
|
"properties": {
|
|
1055
1076
|
"provider": { "type": "string", "minLength": 1, "description": "Canonical provider id (lowercase ASCII, e.g. \"anthropic\", \"openai\", \"google\"). Same value as the `openwop.cost.provider` OTel attribute." },
|
|
@@ -1060,6 +1081,8 @@
|
|
|
1060
1081
|
"costEstimateUsd": { "type": "number", "minimum": 0, "description": "ADVISORY estimate in USD computed by the host's static rate table. MUST NOT be used for billing — real billing is external. Hosts SHOULD omit when no rate is known rather than emit 0." },
|
|
1061
1082
|
"currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "ISO 4217 code when `costEstimateUsd` is non-USD; the field name stays `costEstimateUsd` for back-compat but `currency` overrides the implied denomination." },
|
|
1062
1083
|
"cacheHit": { "type": "boolean", "description": "True iff this call was served from the LLM response cache per `replay.md §\"LLM cache-key recipe\"`. When true, inputTokens/outputTokens reflect the ORIGINAL call's billed values; the cached invocation incurred zero new provider cost." },
|
|
1084
|
+
"cacheReadTokens": { "type": "integer", "minimum": 0, "description": "RFC 0116. Optional, cost-only. Provider-reported tokens served from the prompt-prefix context cache on this call — a cache HIT shows > 0. This is a cost hint, NOT a semantic input: it MAY be omitted on replay (NOT replay-asserted, like `costEstimateUsd`), and a hit-vs-miss difference MUST NOT change `inputTokens`/`outputTokens` or the recorded envelope (RFC 0116 replay-invariance MUST). MUST NOT carry prompt/response substrings per `SECURITY/threat-model-secret-leakage.md §SR-1`." },
|
|
1085
|
+
"cacheWriteTokens": { "type": "integer", "minimum": 0, "description": "RFC 0116. Optional, cost-only. Provider-reported tokens written to the prompt-prefix context cache on this call (a cache PRIME). MAY be omitted on replay (NOT replay-asserted). MUST NOT change `inputTokens`/`outputTokens` or the recorded envelope, and MUST NOT carry prompt/response substrings (SR-1)." },
|
|
1063
1086
|
"nodeId": { "type": "string", "description": "The node id that initiated the provider call. Required for per-node cost attribution dashboards." },
|
|
1064
1087
|
"traceId": { "type": "string", "description": "OTel trace id linking this event to the matching `openwop.cost.*` span. Lets observability backends correlate event-log entries with traces." }
|
|
1065
1088
|
},
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/ui-plugin-message.schema.json",
|
|
4
|
+
"title": "UiPluginMessage",
|
|
5
|
+
"description": "RFC 0117. The `ui-plugin/1` `postMessage` host-RPC envelope exchanged between a sandboxed front-end plugin and its host across the cross-origin-iframe boundary. Every message carries `openwop: \"ui-plugin/1\"` (the protocol version tag — a host MUST ignore messages whose tag it does not recognize) and is one of: a plugin→host `request` (a `method` from the closed allowlist + a monotonic `id` for response correlation), a host→plugin `response` (`ok` + `result` on success, or `ok:false` + `error` on failure), or a host→plugin `event` (a one-way notification, e.g. `host.themeChanged`). The host exposes ONLY the methods the plugin declared in its manifest `hostApi` AND that the host recognizes; any other method MUST be rejected with an `error` response, never silently executed (`frontend-plugin-rpc-allowlist`). `artifact.write` carries the optimistic-concurrency `version` token (RFC 0117 §Concurrency, modeled on the RFC 0059 `host.workspace` `If-Match`/ETag pattern). See `spec/v1/frontend-plugin-packs.md`.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/$defs/request" },
|
|
8
|
+
{ "$ref": "#/$defs/response" },
|
|
9
|
+
{ "$ref": "#/$defs/event" }
|
|
10
|
+
],
|
|
11
|
+
"$defs": {
|
|
12
|
+
"request": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"title": "UiPluginRequest",
|
|
15
|
+
"description": "Plugin→host RPC call. The host MUST reject any `method` not in BOTH the plugin's declared `hostApi` allowlist AND the host's advertised `capabilities.uiPlugins.hostApi` set, returning a `response` with `ok:false` and `error.code: \"method_not_allowed\"`.",
|
|
16
|
+
"required": ["openwop", "type", "id", "method"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"openwop": { "const": "ui-plugin/1" },
|
|
20
|
+
"type": { "const": "request" },
|
|
21
|
+
"id": {
|
|
22
|
+
"type": "integer",
|
|
23
|
+
"minimum": 0,
|
|
24
|
+
"description": "Monotonic, plugin-minted correlation id. The host's `response` echoes it verbatim."
|
|
25
|
+
},
|
|
26
|
+
"method": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["artifact.read", "artifact.write", "host.toast", "host.navigate"]
|
|
29
|
+
},
|
|
30
|
+
"params": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"description": "Method arguments. For `artifact.write`, `params.version` is REQUIRED and carries the opaque token last returned by `artifact.read`/a prior `artifact.write` (optimistic concurrency, §Concurrency). The host MUST reject a `version` it did not mint with `error.code: \"artifact_conflict\"`.",
|
|
33
|
+
"additionalProperties": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"response": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"title": "UiPluginResponse",
|
|
40
|
+
"description": "Host→plugin reply to a `request`, correlated by `id`. Exactly one of `result` (when `ok:true`) or `error` (when `ok:false`).",
|
|
41
|
+
"required": ["openwop", "type", "id", "ok"],
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"openwop": { "const": "ui-plugin/1" },
|
|
45
|
+
"type": { "const": "response" },
|
|
46
|
+
"id": { "type": "integer", "minimum": 0 },
|
|
47
|
+
"ok": { "type": "boolean" },
|
|
48
|
+
"result": {
|
|
49
|
+
"description": "Present iff `ok:true`. For `artifact.read` the result object carries the typed artifact payload (RFC 0071) plus an opaque `version` token. For a successful `artifact.write` the result carries the NEW opaque `version` (so the editor continues without a re-read).",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": true
|
|
52
|
+
},
|
|
53
|
+
"error": {
|
|
54
|
+
"description": "Present iff `ok:false`.",
|
|
55
|
+
"$ref": "#/$defs/error"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"event": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"title": "UiPluginEvent",
|
|
62
|
+
"description": "Host→plugin one-way notification (no `id`, no response). E.g. `host.themeChanged`. A plugin MUST tolerate unknown event names.",
|
|
63
|
+
"required": ["openwop", "type", "event"],
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"properties": {
|
|
66
|
+
"openwop": { "const": "ui-plugin/1" },
|
|
67
|
+
"type": { "const": "event" },
|
|
68
|
+
"event": { "type": "string" },
|
|
69
|
+
"data": { "type": "object", "additionalProperties": true }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"error": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"required": ["code"],
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"properties": {
|
|
77
|
+
"code": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Stable error code. `method_not_allowed`: the requested `method` is not in the plugin+host allowlist (`frontend-plugin-rpc-allowlist`). `artifact_conflict`: an `artifact.write` carried a stale/unknown `version` — the host MUST NOT persist and MUST return the current `version` in `currentVersion` (optimistic concurrency, parallel to RFC 0059 `workspace_conflict`). `artifact_not_found` / `unauthorized` / `internal`: standard host-mediated failures.",
|
|
80
|
+
"enum": ["method_not_allowed", "artifact_conflict", "artifact_not_found", "unauthorized", "internal"]
|
|
81
|
+
},
|
|
82
|
+
"message": { "type": "string", "description": "Human-readable detail. MUST NOT carry secret material." },
|
|
83
|
+
"currentVersion": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Present iff `code: \"artifact_conflict\"`. The host's current opaque `version` for the artifact, so the plugin can re-read/merge and retry. Opaque: the plugin MUST round-trip it verbatim and MUST NOT parse or mint it."
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/lib/toolCatalog.ts
CHANGED
|
@@ -72,6 +72,95 @@ export async function driveToolSession(
|
|
|
72
72
|
return (res.json as ToolSessionResult | undefined) ?? {};
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/** A compact descriptor (RFC 0112) — the lossy `?view=compact` projection.
|
|
76
|
+
* Closed field set: `toolId`/`source`/`safetyTier` (+ optional
|
|
77
|
+
* `title`/`description`/`inputSchema`). The index signature lets a scenario
|
|
78
|
+
* assert the heavy fields are ABSENT without a cast. */
|
|
79
|
+
export interface CompactToolDescriptor {
|
|
80
|
+
toolId?: string;
|
|
81
|
+
source?: string;
|
|
82
|
+
safetyTier?: string;
|
|
83
|
+
title?: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
inputSchema?: Record<string, unknown>;
|
|
86
|
+
[k: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** GET the compact tool catalog (RFC 0112 `GET /v1/tools?view=compact`).
|
|
90
|
+
* Returns the `{ tools: CompactToolDescriptor[] }` envelope's `tools` array;
|
|
91
|
+
* null when the host doesn't serve the read (404/405/501) or the body isn't
|
|
92
|
+
* the expected envelope shape. */
|
|
93
|
+
export async function listToolsCompact(): Promise<CompactToolDescriptor[] | null> {
|
|
94
|
+
const res = await driver.get('/v1/tools?view=compact');
|
|
95
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) return null;
|
|
96
|
+
const body = res.json;
|
|
97
|
+
if (!body || typeof body !== 'object') return null;
|
|
98
|
+
const tools = (body as { tools?: unknown }).tools;
|
|
99
|
+
return Array.isArray(tools) ? (tools as CompactToolDescriptor[]) : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Heavy `ToolDescriptor` fields that a `CompactToolDescriptor` MUST drop
|
|
103
|
+
* (RFC 0112). */
|
|
104
|
+
export const COMPACT_DROPPED_FIELDS = [
|
|
105
|
+
'outputSchema',
|
|
106
|
+
'auth',
|
|
107
|
+
'egress',
|
|
108
|
+
'approval',
|
|
109
|
+
'replayPolicy',
|
|
110
|
+
'costHint',
|
|
111
|
+
'latencyHint',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
/** JSON-Schema keywords a compact `inputSchema` MUST NOT use (RFC 0112 compact
|
|
115
|
+
* structural subset). */
|
|
116
|
+
export const COMPACT_INPUT_SCHEMA_BANNED = [
|
|
117
|
+
'$ref',
|
|
118
|
+
'oneOf',
|
|
119
|
+
'allOf',
|
|
120
|
+
'anyOf',
|
|
121
|
+
'not',
|
|
122
|
+
'patternProperties',
|
|
123
|
+
'dependentSchemas',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/** Schema-bearing keywords whose VALUES are subschemas the compact subset still
|
|
127
|
+
* permits (object/array nesting). We recurse into these — but NOT into property
|
|
128
|
+
* *names* — so a tool field literally named `oneOf` is not a false positive. */
|
|
129
|
+
const COMPACT_SUBSCHEMA_KEYWORDS = ['items', 'additionalProperties', 'contains', 'propertyNames'];
|
|
130
|
+
|
|
131
|
+
/** Recursively searches a compact `inputSchema` for any banned keyword in SCHEMA
|
|
132
|
+
* position at ANY nesting depth (RFC 0112's structural subset is total, not just
|
|
133
|
+
* top-level — a nested `oneOf`/`$ref` is exactly the verbosity the compact view
|
|
134
|
+
* exists to drop). Schema-aware: it checks keywords in schema position and
|
|
135
|
+
* recurses only into subschema-bearing positions (`properties` values, `items`,
|
|
136
|
+
* `additionalProperties`, `prefixItems`, …), never treating a property NAME as a
|
|
137
|
+
* keyword. Returns the first offending keyword, or null when clean. */
|
|
138
|
+
export function findBannedInputSchemaKeyword(schema: unknown): string | null {
|
|
139
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return null;
|
|
140
|
+
const obj = schema as Record<string, unknown>;
|
|
141
|
+
for (const kw of COMPACT_INPUT_SCHEMA_BANNED) {
|
|
142
|
+
if (kw in obj) return kw;
|
|
143
|
+
}
|
|
144
|
+
const props = obj.properties;
|
|
145
|
+
if (props && typeof props === 'object' && !Array.isArray(props)) {
|
|
146
|
+
for (const sub of Object.values(props as Record<string, unknown>)) {
|
|
147
|
+
const hit = findBannedInputSchemaKeyword(sub);
|
|
148
|
+
if (hit) return hit;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const key of COMPACT_SUBSCHEMA_KEYWORDS) {
|
|
152
|
+
const hit = findBannedInputSchemaKeyword(obj[key]);
|
|
153
|
+
if (hit) return hit;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(obj.prefixItems)) {
|
|
156
|
+
for (const sub of obj.prefixItems) {
|
|
157
|
+
const hit = findBannedInputSchemaKeyword(sub);
|
|
158
|
+
if (hit) return hit;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
75
164
|
/** The closed tool-source vocabulary (RFC 0078 §C). */
|
|
76
165
|
export const TOOL_SOURCES = ['node-pack', 'workflow', 'mcp', 'connector', 'host-extension'];
|
|
77
166
|
/** The closed safety-tier vocabulary (RFC 0078 §C). */
|