@canonmsg/backend-contracts 8.2.0 → 8.3.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/cjs/index.js +1 -0
- package/dist/cjs/runtimeDescriptor.js +184 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtimeDescriptor.d.ts +88 -0
- package/dist/runtimeDescriptor.js +175 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -35,3 +35,4 @@ __exportStar(require("./firestoreValues.js"), exports);
|
|
|
35
35
|
__exportStar(require("./moderation.js"), exports);
|
|
36
36
|
__exportStar(require("./selfContext.js"), exports);
|
|
37
37
|
__exportStar(require("./replyAuthority.js"), exports);
|
|
38
|
+
__exportStar(require("./runtimeDescriptor.js"), exports);
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_PUBLIC_RUNTIME_COMMANDS = void 0;
|
|
4
|
+
exports.normalizeRuntimeCommandAlias = normalizeRuntimeCommandAlias;
|
|
5
|
+
exports.normalizeRuntimeCommandAliases = normalizeRuntimeCommandAliases;
|
|
6
|
+
exports.isRuntimePrimitiveId = isRuntimePrimitiveId;
|
|
7
|
+
exports.normalizePublicRuntimeCommands = normalizePublicRuntimeCommands;
|
|
8
|
+
exports.normalizePublicRuntimeDescriptor = normalizePublicRuntimeDescriptor;
|
|
9
|
+
exports.normalizePublicRuntimeFacts = normalizePublicRuntimeFacts;
|
|
10
|
+
function normalizeRuntimeCommandAlias(value) {
|
|
11
|
+
const normalized = value.trim().replace(/^\/+/, '').replace(/\s+/g, '-');
|
|
12
|
+
return /^[A-Za-z0-9:_-]+$/.test(normalized) ? normalized : null;
|
|
13
|
+
}
|
|
14
|
+
function normalizeRuntimeCommandAliases(command) {
|
|
15
|
+
const rawAliases = command.aliases?.length ? command.aliases : [command.id];
|
|
16
|
+
const aliases = rawAliases
|
|
17
|
+
.map(normalizeRuntimeCommandAlias)
|
|
18
|
+
.filter((value) => Boolean(value));
|
|
19
|
+
return Array.from(new Set(aliases));
|
|
20
|
+
}
|
|
21
|
+
exports.MAX_PUBLIC_RUNTIME_COMMANDS = 256;
|
|
22
|
+
function record(value) {
|
|
23
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
24
|
+
? value : {};
|
|
25
|
+
}
|
|
26
|
+
function text(value, max) {
|
|
27
|
+
return typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;
|
|
28
|
+
}
|
|
29
|
+
function member(value, choices) {
|
|
30
|
+
return choices.includes(value) ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function members(value, choices) {
|
|
33
|
+
if (!Array.isArray(value))
|
|
34
|
+
return undefined;
|
|
35
|
+
return [...new Set(value.filter((entry) => choices.includes(entry)))];
|
|
36
|
+
}
|
|
37
|
+
function defined(value) {
|
|
38
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
39
|
+
}
|
|
40
|
+
const PRIMITIVES = [
|
|
41
|
+
'runtime.status', 'runtime.reasoning.set', 'runtime.verbosity.set', 'runtime.usage',
|
|
42
|
+
'context.compact', 'session.new', 'session.reset',
|
|
43
|
+
];
|
|
44
|
+
function isRuntimePrimitiveId(value) {
|
|
45
|
+
return typeof value === 'string' && PRIMITIVES.includes(value);
|
|
46
|
+
}
|
|
47
|
+
function normalizeDispatch(value) {
|
|
48
|
+
const dispatch = record(value);
|
|
49
|
+
switch (dispatch.kind) {
|
|
50
|
+
case 'signal': {
|
|
51
|
+
const signal = member(dispatch.signal, ['interrupt', 'stop_and_drop', 'new_session']);
|
|
52
|
+
return signal ? { kind: 'signal', signal } : undefined;
|
|
53
|
+
}
|
|
54
|
+
case 'primitive': {
|
|
55
|
+
const primitive = member(dispatch.primitive, PRIMITIVES);
|
|
56
|
+
return primitive ? { kind: 'primitive', primitive } : undefined;
|
|
57
|
+
}
|
|
58
|
+
case 'control': {
|
|
59
|
+
const controlId = text(dispatch.controlId, 128);
|
|
60
|
+
return controlId ? defined({ kind: 'control', controlId, value: text(dispatch.value, 1024) }) : undefined;
|
|
61
|
+
}
|
|
62
|
+
case 'text_passthrough': {
|
|
63
|
+
const template = text(dispatch.template, 2048);
|
|
64
|
+
return template ? { kind: 'text_passthrough', template } : undefined;
|
|
65
|
+
}
|
|
66
|
+
case 'compose': {
|
|
67
|
+
const content = text(dispatch.text, 2048);
|
|
68
|
+
return content ? { kind: 'compose', text: content } : undefined;
|
|
69
|
+
}
|
|
70
|
+
case 'open_details':
|
|
71
|
+
return defined({ kind: 'open_details', target: text(dispatch.target, 128) });
|
|
72
|
+
default:
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function normalizeArgument(value) {
|
|
77
|
+
const arg = record(value);
|
|
78
|
+
const id = text(arg.id, 128);
|
|
79
|
+
const label = text(arg.label, 160);
|
|
80
|
+
const kind = member(arg.kind, ['string', 'enum', 'boolean']);
|
|
81
|
+
if (!id || !label || !kind)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (['required', 'captureRemaining'].some((key) => arg[key] !== undefined && typeof arg[key] !== 'boolean'))
|
|
84
|
+
return undefined;
|
|
85
|
+
if (arg.choices !== undefined && !Array.isArray(arg.choices))
|
|
86
|
+
return undefined;
|
|
87
|
+
const choices = Array.isArray(arg.choices) ? arg.choices.slice(0, 128).flatMap((value) => {
|
|
88
|
+
const choice = record(value);
|
|
89
|
+
const entry = text(choice.value, 1024);
|
|
90
|
+
const label = text(choice.label, 160);
|
|
91
|
+
return entry && label ? [defined({ value: entry, label, description: text(choice.description, 1024) })] : [];
|
|
92
|
+
}) : undefined;
|
|
93
|
+
// Do not advertise an argument whose required enum has no usable choices.
|
|
94
|
+
if (kind === 'enum' && !choices?.length)
|
|
95
|
+
return undefined;
|
|
96
|
+
return defined({ id, label, kind, choices,
|
|
97
|
+
required: typeof arg.required === 'boolean' ? arg.required : undefined,
|
|
98
|
+
captureRemaining: typeof arg.captureRemaining === 'boolean' ? arg.captureRemaining : undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Public command metadata shared by REST and direct RTDB publishers. Never copies local configuration. */
|
|
102
|
+
function normalizePublicRuntimeCommands(value) {
|
|
103
|
+
if (!Array.isArray(value))
|
|
104
|
+
return [];
|
|
105
|
+
const seenIds = new Set();
|
|
106
|
+
const seenAliases = new Set();
|
|
107
|
+
const result = [];
|
|
108
|
+
for (const item of value.slice(0, exports.MAX_PUBLIC_RUNTIME_COMMANDS)) {
|
|
109
|
+
const command = record(item);
|
|
110
|
+
const id = text(command.id, 128);
|
|
111
|
+
const label = text(command.label, 160);
|
|
112
|
+
const dispatch = normalizeDispatch(command.dispatch);
|
|
113
|
+
if (!id || !label || !dispatch || seenIds.has(id)
|
|
114
|
+
|| command.visibility === 'hidden' || command.sensitive === true)
|
|
115
|
+
continue;
|
|
116
|
+
if (['ownerOnly', 'sensitive'].some((key) => command[key] !== undefined && typeof command[key] !== 'boolean'))
|
|
117
|
+
continue;
|
|
118
|
+
if (['aliases', 'args', 'placements', 'availability'].some((key) => command[key] !== undefined && !Array.isArray(command[key])))
|
|
119
|
+
continue;
|
|
120
|
+
const rawAliases = Array.isArray(command.aliases) && command.aliases.length ? command.aliases : [id];
|
|
121
|
+
const aliases = [...new Set(rawAliases.slice(0, 8).flatMap((value) => {
|
|
122
|
+
const alias = typeof value === 'string' && value.length <= 128 ? normalizeRuntimeCommandAlias(value) : null;
|
|
123
|
+
return alias && /^[A-Za-z0-9:_-]+$/.test(alias) && !seenAliases.has(alias.toLowerCase()) ? [alias] : [];
|
|
124
|
+
}))];
|
|
125
|
+
if (!aliases.length)
|
|
126
|
+
continue;
|
|
127
|
+
const args = Array.isArray(command.args) ? command.args.slice(0, 12).map(normalizeArgument) : undefined;
|
|
128
|
+
// A malformed argument must not silently turn a parameterized action into a different command.
|
|
129
|
+
if (args?.some((arg) => !arg) || (Array.isArray(command.args) && command.args.length > 12))
|
|
130
|
+
continue;
|
|
131
|
+
seenIds.add(id);
|
|
132
|
+
aliases.forEach((alias) => seenAliases.add(alias.toLowerCase()));
|
|
133
|
+
result.push(defined({ id, label, aliases, dispatch,
|
|
134
|
+
args: args,
|
|
135
|
+
description: text(command.description, 1024),
|
|
136
|
+
category: member(command.category, ['plan', 'turn', 'session', 'runtime', 'details', 'skill', 'custom']),
|
|
137
|
+
placements: members(command.placements, ['composer_slash', 'command_palette', 'session_strip']),
|
|
138
|
+
availability: members(command.availability, ['idle', 'busy', 'busy_with_queue', 'waiting_input', 'always']),
|
|
139
|
+
ownerOnly: typeof command.ownerOnly === 'boolean' ? command.ownerOnly : undefined,
|
|
140
|
+
disabledReason: text(command.disabledReason, 1024),
|
|
141
|
+
trailingTextBehavior: member(command.trailingTextBehavior, ['ignore', 'send_as_prompt']),
|
|
142
|
+
primitive: member(command.primitive, PRIMITIVES),
|
|
143
|
+
tier: member(command.tier, ['primary', 'detail', 'diagnostic']),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function normalizePublicRuntimeDescriptor(value) {
|
|
149
|
+
const descriptor = record(value);
|
|
150
|
+
return defined({
|
|
151
|
+
supportsInterrupt: typeof descriptor.supportsInterrupt === 'boolean' ? descriptor.supportsInterrupt : undefined,
|
|
152
|
+
supportsInputInterrupt: typeof descriptor.supportsInputInterrupt === 'boolean' ? descriptor.supportsInputInterrupt : undefined,
|
|
153
|
+
streamingTextMode: member(descriptor.streamingTextMode, ['none', 'status', 'snapshot', 'block', 'delta']),
|
|
154
|
+
commands: Array.isArray(descriptor.commands) ? normalizePublicRuntimeCommands(descriptor.commands) : undefined,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const PUBLIC_RUNTIME_FACT_GROUPS = {
|
|
158
|
+
harness: 'runtime', runtime: 'runtime', provider: 'model', model: 'model',
|
|
159
|
+
reasoning: 'model', connection: 'connection', gateway: 'connection', route: 'route',
|
|
160
|
+
};
|
|
161
|
+
/** Read-only session facts, excluding local paths, inventories, and hidden/sensitive values. */
|
|
162
|
+
function normalizePublicRuntimeFacts(value) {
|
|
163
|
+
if (!Array.isArray(value))
|
|
164
|
+
return [];
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
return value.slice(0, 16).flatMap((entry) => {
|
|
167
|
+
const fact = record(entry);
|
|
168
|
+
const id = text(fact.id, 64);
|
|
169
|
+
const group = id && Object.hasOwn(PUBLIC_RUNTIME_FACT_GROUPS, id)
|
|
170
|
+
? PUBLIC_RUNTIME_FACT_GROUPS[id] : undefined;
|
|
171
|
+
const label = text(fact.label, 80);
|
|
172
|
+
const content = text(fact.value, 512);
|
|
173
|
+
if (!id || !group || !label || !content || seen.has(id)
|
|
174
|
+
|| fact.sensitive === true || fact.visibility === 'hidden')
|
|
175
|
+
return [];
|
|
176
|
+
seen.add(id);
|
|
177
|
+
return [defined({ id, group, label, value: content,
|
|
178
|
+
tier: member(fact.tier, ['primary', 'detail', 'diagnostic']),
|
|
179
|
+
tone: member(fact.tone, ['neutral', 'good', 'warning', 'danger']),
|
|
180
|
+
copyable: typeof fact.copyable === 'boolean' ? fact.copyable : undefined,
|
|
181
|
+
updatedAt: typeof fact.updatedAt === 'number' && Number.isFinite(fact.updatedAt) && fact.updatedAt >= 0
|
|
182
|
+
? fact.updatedAt : undefined, })];
|
|
183
|
+
});
|
|
184
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type CanonControlValue = string;
|
|
2
|
+
export type CanonRuntimeStreamingMode = 'none' | 'status' | 'snapshot' | 'block' | 'delta';
|
|
3
|
+
export type CanonRuntimeDetailTier = 'primary' | 'detail' | 'diagnostic';
|
|
4
|
+
export type CanonRuntimeVisibility = 'conversation' | 'hidden';
|
|
5
|
+
export type CanonRuntimeActionAvailability = 'idle' | 'busy' | 'busy_with_queue' | 'waiting_input' | 'always';
|
|
6
|
+
export type CanonRuntimeActionPlacement = 'composer_slash' | 'command_palette' | 'session_strip';
|
|
7
|
+
export type CanonRuntimeActionCategory = 'plan' | 'turn' | 'session' | 'runtime' | 'details' | 'skill' | 'custom';
|
|
8
|
+
export type CanonRuntimePrimitiveId = 'runtime.status' | 'runtime.reasoning.set' | 'runtime.verbosity.set' | 'runtime.usage' | 'context.compact' | 'session.new' | 'session.reset';
|
|
9
|
+
export type CanonRuntimeCommandArgumentKind = 'string' | 'enum' | 'boolean';
|
|
10
|
+
export interface CanonRuntimeCommandArgumentChoice {
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CanonRuntimeCommandArgumentDescriptor {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
kind: CanonRuntimeCommandArgumentKind;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
captureRemaining?: boolean;
|
|
21
|
+
choices?: ReadonlyArray<CanonRuntimeCommandArgumentChoice>;
|
|
22
|
+
}
|
|
23
|
+
export type CanonRuntimeActionDispatch = {
|
|
24
|
+
kind: 'control';
|
|
25
|
+
controlId: string;
|
|
26
|
+
value?: CanonControlValue;
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'signal';
|
|
29
|
+
signal: 'interrupt' | 'stop_and_drop' | 'new_session';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'primitive';
|
|
32
|
+
primitive: CanonRuntimePrimitiveId;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'text_passthrough';
|
|
35
|
+
template: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'compose';
|
|
38
|
+
text: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'open_details';
|
|
41
|
+
target?: string;
|
|
42
|
+
};
|
|
43
|
+
export interface CanonRuntimeActionDescriptor {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
visibility?: CanonRuntimeVisibility;
|
|
48
|
+
tier?: CanonRuntimeDetailTier;
|
|
49
|
+
sensitive?: boolean;
|
|
50
|
+
primitive?: CanonRuntimePrimitiveId;
|
|
51
|
+
aliases?: ReadonlyArray<string>;
|
|
52
|
+
category?: CanonRuntimeActionCategory;
|
|
53
|
+
placements?: ReadonlyArray<CanonRuntimeActionPlacement>;
|
|
54
|
+
availability?: ReadonlyArray<CanonRuntimeActionAvailability>;
|
|
55
|
+
ownerOnly?: boolean;
|
|
56
|
+
disabledReason?: string | null;
|
|
57
|
+
trailingTextBehavior?: 'ignore' | 'send_as_prompt';
|
|
58
|
+
args?: ReadonlyArray<CanonRuntimeCommandArgumentDescriptor>;
|
|
59
|
+
dispatch: CanonRuntimeActionDispatch;
|
|
60
|
+
}
|
|
61
|
+
export interface CanonRuntimeCommandDescriptor extends CanonRuntimeActionDescriptor {
|
|
62
|
+
primitive?: CanonRuntimePrimitiveId;
|
|
63
|
+
args?: ReadonlyArray<CanonRuntimeCommandArgumentDescriptor>;
|
|
64
|
+
}
|
|
65
|
+
export declare function normalizeRuntimeCommandAlias(value: string): string | null;
|
|
66
|
+
export declare function normalizeRuntimeCommandAliases(command: Pick<CanonRuntimeCommandDescriptor, 'id' | 'aliases'>): string[];
|
|
67
|
+
export declare const MAX_PUBLIC_RUNTIME_COMMANDS = 256;
|
|
68
|
+
export declare function isRuntimePrimitiveId(value: unknown): value is CanonRuntimePrimitiveId;
|
|
69
|
+
/** Public command metadata shared by REST and direct RTDB publishers. Never copies local configuration. */
|
|
70
|
+
export declare function normalizePublicRuntimeCommands(value: unknown): CanonRuntimeCommandDescriptor[];
|
|
71
|
+
export interface PublicRuntimeDescriptor {
|
|
72
|
+
supportsInterrupt?: boolean;
|
|
73
|
+
supportsInputInterrupt?: boolean;
|
|
74
|
+
streamingTextMode?: CanonRuntimeStreamingMode;
|
|
75
|
+
commands?: CanonRuntimeCommandDescriptor[];
|
|
76
|
+
}
|
|
77
|
+
export declare function normalizePublicRuntimeDescriptor(value: unknown): PublicRuntimeDescriptor;
|
|
78
|
+
/** Read-only session facts, excluding local paths, inventories, and hidden/sensitive values. */
|
|
79
|
+
export declare function normalizePublicRuntimeFacts(value: unknown): {
|
|
80
|
+
id: string;
|
|
81
|
+
group: "runtime" | "model" | "connection" | "route";
|
|
82
|
+
label: string;
|
|
83
|
+
value: string;
|
|
84
|
+
tier: "primary" | "detail" | "diagnostic" | undefined;
|
|
85
|
+
tone: "neutral" | "good" | "warning" | "danger" | undefined;
|
|
86
|
+
copyable: boolean | undefined;
|
|
87
|
+
updatedAt: number | undefined;
|
|
88
|
+
}[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export function normalizeRuntimeCommandAlias(value) {
|
|
2
|
+
const normalized = value.trim().replace(/^\/+/, '').replace(/\s+/g, '-');
|
|
3
|
+
return /^[A-Za-z0-9:_-]+$/.test(normalized) ? normalized : null;
|
|
4
|
+
}
|
|
5
|
+
export function normalizeRuntimeCommandAliases(command) {
|
|
6
|
+
const rawAliases = command.aliases?.length ? command.aliases : [command.id];
|
|
7
|
+
const aliases = rawAliases
|
|
8
|
+
.map(normalizeRuntimeCommandAlias)
|
|
9
|
+
.filter((value) => Boolean(value));
|
|
10
|
+
return Array.from(new Set(aliases));
|
|
11
|
+
}
|
|
12
|
+
export const MAX_PUBLIC_RUNTIME_COMMANDS = 256;
|
|
13
|
+
function record(value) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
+
? value : {};
|
|
16
|
+
}
|
|
17
|
+
function text(value, max) {
|
|
18
|
+
return typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
function member(value, choices) {
|
|
21
|
+
return choices.includes(value) ? value : undefined;
|
|
22
|
+
}
|
|
23
|
+
function members(value, choices) {
|
|
24
|
+
if (!Array.isArray(value))
|
|
25
|
+
return undefined;
|
|
26
|
+
return [...new Set(value.filter((entry) => choices.includes(entry)))];
|
|
27
|
+
}
|
|
28
|
+
function defined(value) {
|
|
29
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
30
|
+
}
|
|
31
|
+
const PRIMITIVES = [
|
|
32
|
+
'runtime.status', 'runtime.reasoning.set', 'runtime.verbosity.set', 'runtime.usage',
|
|
33
|
+
'context.compact', 'session.new', 'session.reset',
|
|
34
|
+
];
|
|
35
|
+
export function isRuntimePrimitiveId(value) {
|
|
36
|
+
return typeof value === 'string' && PRIMITIVES.includes(value);
|
|
37
|
+
}
|
|
38
|
+
function normalizeDispatch(value) {
|
|
39
|
+
const dispatch = record(value);
|
|
40
|
+
switch (dispatch.kind) {
|
|
41
|
+
case 'signal': {
|
|
42
|
+
const signal = member(dispatch.signal, ['interrupt', 'stop_and_drop', 'new_session']);
|
|
43
|
+
return signal ? { kind: 'signal', signal } : undefined;
|
|
44
|
+
}
|
|
45
|
+
case 'primitive': {
|
|
46
|
+
const primitive = member(dispatch.primitive, PRIMITIVES);
|
|
47
|
+
return primitive ? { kind: 'primitive', primitive } : undefined;
|
|
48
|
+
}
|
|
49
|
+
case 'control': {
|
|
50
|
+
const controlId = text(dispatch.controlId, 128);
|
|
51
|
+
return controlId ? defined({ kind: 'control', controlId, value: text(dispatch.value, 1024) }) : undefined;
|
|
52
|
+
}
|
|
53
|
+
case 'text_passthrough': {
|
|
54
|
+
const template = text(dispatch.template, 2048);
|
|
55
|
+
return template ? { kind: 'text_passthrough', template } : undefined;
|
|
56
|
+
}
|
|
57
|
+
case 'compose': {
|
|
58
|
+
const content = text(dispatch.text, 2048);
|
|
59
|
+
return content ? { kind: 'compose', text: content } : undefined;
|
|
60
|
+
}
|
|
61
|
+
case 'open_details':
|
|
62
|
+
return defined({ kind: 'open_details', target: text(dispatch.target, 128) });
|
|
63
|
+
default:
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeArgument(value) {
|
|
68
|
+
const arg = record(value);
|
|
69
|
+
const id = text(arg.id, 128);
|
|
70
|
+
const label = text(arg.label, 160);
|
|
71
|
+
const kind = member(arg.kind, ['string', 'enum', 'boolean']);
|
|
72
|
+
if (!id || !label || !kind)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (['required', 'captureRemaining'].some((key) => arg[key] !== undefined && typeof arg[key] !== 'boolean'))
|
|
75
|
+
return undefined;
|
|
76
|
+
if (arg.choices !== undefined && !Array.isArray(arg.choices))
|
|
77
|
+
return undefined;
|
|
78
|
+
const choices = Array.isArray(arg.choices) ? arg.choices.slice(0, 128).flatMap((value) => {
|
|
79
|
+
const choice = record(value);
|
|
80
|
+
const entry = text(choice.value, 1024);
|
|
81
|
+
const label = text(choice.label, 160);
|
|
82
|
+
return entry && label ? [defined({ value: entry, label, description: text(choice.description, 1024) })] : [];
|
|
83
|
+
}) : undefined;
|
|
84
|
+
// Do not advertise an argument whose required enum has no usable choices.
|
|
85
|
+
if (kind === 'enum' && !choices?.length)
|
|
86
|
+
return undefined;
|
|
87
|
+
return defined({ id, label, kind, choices,
|
|
88
|
+
required: typeof arg.required === 'boolean' ? arg.required : undefined,
|
|
89
|
+
captureRemaining: typeof arg.captureRemaining === 'boolean' ? arg.captureRemaining : undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Public command metadata shared by REST and direct RTDB publishers. Never copies local configuration. */
|
|
93
|
+
export function normalizePublicRuntimeCommands(value) {
|
|
94
|
+
if (!Array.isArray(value))
|
|
95
|
+
return [];
|
|
96
|
+
const seenIds = new Set();
|
|
97
|
+
const seenAliases = new Set();
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const item of value.slice(0, MAX_PUBLIC_RUNTIME_COMMANDS)) {
|
|
100
|
+
const command = record(item);
|
|
101
|
+
const id = text(command.id, 128);
|
|
102
|
+
const label = text(command.label, 160);
|
|
103
|
+
const dispatch = normalizeDispatch(command.dispatch);
|
|
104
|
+
if (!id || !label || !dispatch || seenIds.has(id)
|
|
105
|
+
|| command.visibility === 'hidden' || command.sensitive === true)
|
|
106
|
+
continue;
|
|
107
|
+
if (['ownerOnly', 'sensitive'].some((key) => command[key] !== undefined && typeof command[key] !== 'boolean'))
|
|
108
|
+
continue;
|
|
109
|
+
if (['aliases', 'args', 'placements', 'availability'].some((key) => command[key] !== undefined && !Array.isArray(command[key])))
|
|
110
|
+
continue;
|
|
111
|
+
const rawAliases = Array.isArray(command.aliases) && command.aliases.length ? command.aliases : [id];
|
|
112
|
+
const aliases = [...new Set(rawAliases.slice(0, 8).flatMap((value) => {
|
|
113
|
+
const alias = typeof value === 'string' && value.length <= 128 ? normalizeRuntimeCommandAlias(value) : null;
|
|
114
|
+
return alias && /^[A-Za-z0-9:_-]+$/.test(alias) && !seenAliases.has(alias.toLowerCase()) ? [alias] : [];
|
|
115
|
+
}))];
|
|
116
|
+
if (!aliases.length)
|
|
117
|
+
continue;
|
|
118
|
+
const args = Array.isArray(command.args) ? command.args.slice(0, 12).map(normalizeArgument) : undefined;
|
|
119
|
+
// A malformed argument must not silently turn a parameterized action into a different command.
|
|
120
|
+
if (args?.some((arg) => !arg) || (Array.isArray(command.args) && command.args.length > 12))
|
|
121
|
+
continue;
|
|
122
|
+
seenIds.add(id);
|
|
123
|
+
aliases.forEach((alias) => seenAliases.add(alias.toLowerCase()));
|
|
124
|
+
result.push(defined({ id, label, aliases, dispatch,
|
|
125
|
+
args: args,
|
|
126
|
+
description: text(command.description, 1024),
|
|
127
|
+
category: member(command.category, ['plan', 'turn', 'session', 'runtime', 'details', 'skill', 'custom']),
|
|
128
|
+
placements: members(command.placements, ['composer_slash', 'command_palette', 'session_strip']),
|
|
129
|
+
availability: members(command.availability, ['idle', 'busy', 'busy_with_queue', 'waiting_input', 'always']),
|
|
130
|
+
ownerOnly: typeof command.ownerOnly === 'boolean' ? command.ownerOnly : undefined,
|
|
131
|
+
disabledReason: text(command.disabledReason, 1024),
|
|
132
|
+
trailingTextBehavior: member(command.trailingTextBehavior, ['ignore', 'send_as_prompt']),
|
|
133
|
+
primitive: member(command.primitive, PRIMITIVES),
|
|
134
|
+
tier: member(command.tier, ['primary', 'detail', 'diagnostic']),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
export function normalizePublicRuntimeDescriptor(value) {
|
|
140
|
+
const descriptor = record(value);
|
|
141
|
+
return defined({
|
|
142
|
+
supportsInterrupt: typeof descriptor.supportsInterrupt === 'boolean' ? descriptor.supportsInterrupt : undefined,
|
|
143
|
+
supportsInputInterrupt: typeof descriptor.supportsInputInterrupt === 'boolean' ? descriptor.supportsInputInterrupt : undefined,
|
|
144
|
+
streamingTextMode: member(descriptor.streamingTextMode, ['none', 'status', 'snapshot', 'block', 'delta']),
|
|
145
|
+
commands: Array.isArray(descriptor.commands) ? normalizePublicRuntimeCommands(descriptor.commands) : undefined,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const PUBLIC_RUNTIME_FACT_GROUPS = {
|
|
149
|
+
harness: 'runtime', runtime: 'runtime', provider: 'model', model: 'model',
|
|
150
|
+
reasoning: 'model', connection: 'connection', gateway: 'connection', route: 'route',
|
|
151
|
+
};
|
|
152
|
+
/** Read-only session facts, excluding local paths, inventories, and hidden/sensitive values. */
|
|
153
|
+
export function normalizePublicRuntimeFacts(value) {
|
|
154
|
+
if (!Array.isArray(value))
|
|
155
|
+
return [];
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
return value.slice(0, 16).flatMap((entry) => {
|
|
158
|
+
const fact = record(entry);
|
|
159
|
+
const id = text(fact.id, 64);
|
|
160
|
+
const group = id && Object.hasOwn(PUBLIC_RUNTIME_FACT_GROUPS, id)
|
|
161
|
+
? PUBLIC_RUNTIME_FACT_GROUPS[id] : undefined;
|
|
162
|
+
const label = text(fact.label, 80);
|
|
163
|
+
const content = text(fact.value, 512);
|
|
164
|
+
if (!id || !group || !label || !content || seen.has(id)
|
|
165
|
+
|| fact.sensitive === true || fact.visibility === 'hidden')
|
|
166
|
+
return [];
|
|
167
|
+
seen.add(id);
|
|
168
|
+
return [defined({ id, group, label, value: content,
|
|
169
|
+
tier: member(fact.tier, ['primary', 'detail', 'diagnostic']),
|
|
170
|
+
tone: member(fact.tone, ['neutral', 'good', 'warning', 'danger']),
|
|
171
|
+
copyable: typeof fact.copyable === 'boolean' ? fact.copyable : undefined,
|
|
172
|
+
updatedAt: typeof fact.updatedAt === 'number' && Number.isFinite(fact.updatedAt) && fact.updatedAt >= 0
|
|
173
|
+
? fact.updatedAt : undefined, })];
|
|
174
|
+
});
|
|
175
|
+
}
|