@jambonz/schema 0.2.2 → 0.3.1
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.
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://jambonz.org/schema/commands/llm:tool-output",
|
|
4
|
+
"title": "LLM Tool Output Command",
|
|
5
|
+
"description": "Command sent from the application to feature-server over the session websocket, carrying the result of a tool invocation that the LLM initiated during an agent verb. Sent in response to an `agent:tool-call` event. The result is fed back into the LLM so it can continue generating using the tool output.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["type", "command", "tool_call_id", "data"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"type": { "const": "command" },
|
|
10
|
+
"command": { "const": "llm:tool-output" },
|
|
11
|
+
"tool_call_id": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"minLength": 1,
|
|
14
|
+
"description": "The tool_call_id echoed from the originating `agent:tool-call` event."
|
|
15
|
+
},
|
|
16
|
+
"data": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"description": "Tool output payload. `{result: ...}` is the canonical shape — the wrapped value is what the LLM sees as the tool's return value. Other object shapes are accepted (they're JSON-stringified as-is into the tool_result content block on the wire) but `result` is preferred for clarity.",
|
|
19
|
+
"properties": {
|
|
20
|
+
"result": {}
|
|
21
|
+
},
|
|
22
|
+
"additionalProperties": true
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"examples": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "llm:tool-output",
|
|
30
|
+
"tool_call_id": "toolu_017NKXtgnD7fuo1KaTeSM1ok",
|
|
31
|
+
"data": {
|
|
32
|
+
"result": "The current temperature in Boston is 9.3°C with wind speed 17 km/h."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"description": "Custom vocabulary terms."
|
|
48
48
|
},
|
|
49
49
|
"languageModel": { "type": "string", "description": "Language model to use." },
|
|
50
|
-
"audioQueryAbsoluteTimeout": { "type": "number", "description": "Absolute timeout for audio queries." }
|
|
50
|
+
"audioQueryAbsoluteTimeout": { "type": "number", "description": "Absolute timeout for audio queries." },
|
|
51
|
+
"eoqThreshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "End-of-query likelihood threshold (0.0-1.0) to trigger end of speech when segmentation is disabled. Default 0.8, set to 0 to disable." },
|
|
52
|
+
"vadStopThreshold": { "type": "number", "minimum": 0, "maximum": 1, "description": "VAD probability threshold to trigger end of speech when segmentation is disabled. When VAD drops below this value after speech is detected, streaming stops. Default 0.05, set to 0 to disable." }
|
|
51
53
|
},
|
|
52
54
|
"additionalProperties": false
|
|
53
55
|
}
|
package/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
const {validate, validateVerb, validateApp} = require('./lib/validator');
|
|
1
|
+
const {validate, validateVerb, validateApp, validateCommand} = require('./lib/validator');
|
|
2
2
|
const {normalizeJambones} = require('./lib/normalize');
|
|
3
3
|
|
|
4
4
|
module.exports = {
|
|
5
5
|
validate,
|
|
6
6
|
validateVerb,
|
|
7
7
|
validateApp,
|
|
8
|
+
validateCommand,
|
|
8
9
|
normalizeJambones,
|
|
9
10
|
};
|
package/lib/validator.js
CHANGED
|
@@ -54,6 +54,12 @@ function getAjv() {
|
|
|
54
54
|
_ajv.addSchema(schema);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/* register command schemas (app → server over the session websocket) */
|
|
58
|
+
for (const name of discoverSchemas('commands')) {
|
|
59
|
+
const schema = loadSchema(`commands/${name}.schema.json`);
|
|
60
|
+
_ajv.addSchema(schema);
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
/* compile the root app schema */
|
|
58
64
|
const appSchema = loadSchema('jambonz-app.schema.json');
|
|
59
65
|
_validateApp = _ajv.compile(appSchema);
|
|
@@ -103,6 +109,45 @@ function validateVerb(name, data, logger) {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Validate a command message sent from the app to feature-server over the
|
|
114
|
+
* session websocket.
|
|
115
|
+
*
|
|
116
|
+
* Commands are the inverse of callbacks: callbacks flow server → app; commands
|
|
117
|
+
* flow app → server. Currently defined commands:
|
|
118
|
+
* - llm:tool-output — response to an agent:tool-call event.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} name - Command name (e.g. 'llm:tool-output').
|
|
121
|
+
* @param {object} message - The full command message including `type` and `command`.
|
|
122
|
+
* @param {object} logger - Logger instance.
|
|
123
|
+
* @throws {Error} If the command is unknown or validation fails.
|
|
124
|
+
*/
|
|
125
|
+
function validateCommand(name, message, logger) {
|
|
126
|
+
const ajv = getAjv();
|
|
127
|
+
const schemaId = `https://jambonz.org/schema/commands/${name}`;
|
|
128
|
+
const validate = ajv.getSchema(schemaId);
|
|
129
|
+
if (!validate) {
|
|
130
|
+
throw new Error(`invalid command: ${name}`);
|
|
131
|
+
}
|
|
132
|
+
const valid = validate(message);
|
|
133
|
+
if (!valid) {
|
|
134
|
+
const errors = validate.errors || [];
|
|
135
|
+
const details = errors.map((e) => {
|
|
136
|
+
const path = e.instancePath || '(root)';
|
|
137
|
+
let msg = `'${path}': ${e.message}`;
|
|
138
|
+
if (e.params) {
|
|
139
|
+
if (e.params.type) msg += ` (expected ${e.params.type})`;
|
|
140
|
+
if (e.params.allowedValues) msg += ` (allowed: ${e.params.allowedValues.join(', ')})`;
|
|
141
|
+
if (e.params.missingProperty) msg += ` (missing: ${e.params.missingProperty})`;
|
|
142
|
+
}
|
|
143
|
+
return msg;
|
|
144
|
+
}).join('; ');
|
|
145
|
+
const errMsg = `command '${name}' validation failed — ${details}. Schema: ${schemaId}`;
|
|
146
|
+
debug(errMsg);
|
|
147
|
+
throw new Error(errMsg);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
106
151
|
/**
|
|
107
152
|
* Validate a complete jambonz application (array of verbs).
|
|
108
153
|
*
|
|
@@ -144,4 +189,4 @@ function validateApp(app) {
|
|
|
144
189
|
};
|
|
145
190
|
}
|
|
146
191
|
|
|
147
|
-
module.exports = {validate, validateVerb, validateApp};
|
|
192
|
+
module.exports = {validate, validateVerb, validateApp, validateCommand};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jambonz/schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "JSON Schema definitions and validation for jambonz verb applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"verbs/",
|
|
33
33
|
"components/",
|
|
34
34
|
"callbacks/",
|
|
35
|
+
"commands/",
|
|
35
36
|
"docs/",
|
|
36
37
|
"jambonz-app.schema.json",
|
|
37
38
|
"AGENTS.md"
|
package/verbs/agent.schema.json
CHANGED
|
@@ -86,8 +86,90 @@
|
|
|
86
86
|
},
|
|
87
87
|
"llm": {
|
|
88
88
|
"type": "object",
|
|
89
|
-
"description": "LLM configuration for the agent.
|
|
90
|
-
"
|
|
89
|
+
"description": "LLM configuration for the agent.",
|
|
90
|
+
"required": ["vendor", "model"],
|
|
91
|
+
"properties": {
|
|
92
|
+
"vendor": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"enum": [
|
|
95
|
+
"openai",
|
|
96
|
+
"anthropic",
|
|
97
|
+
"google",
|
|
98
|
+
"vertex-gemini",
|
|
99
|
+
"vertex-openai",
|
|
100
|
+
"bedrock",
|
|
101
|
+
"deepseek",
|
|
102
|
+
"azure-openai"
|
|
103
|
+
],
|
|
104
|
+
"description": "LLM vendor id. Must match a `@jambonz/llm` registered adapter."
|
|
105
|
+
},
|
|
106
|
+
"model": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "Vendor-specific model id (e.g. 'gpt-4o', 'claude-sonnet-4-5-20250929')."
|
|
109
|
+
},
|
|
110
|
+
"label": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "Optional label to disambiguate when the account has multiple credentials for the same vendor."
|
|
113
|
+
},
|
|
114
|
+
"auth": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"description": "Optional inline credentials. When omitted, feature-server looks up credentials by (vendor, label) from the database.",
|
|
117
|
+
"properties": {
|
|
118
|
+
"apiKey": { "type": "string" }
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": true
|
|
121
|
+
},
|
|
122
|
+
"connectOptions": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"description": "SDK-level client options.",
|
|
125
|
+
"properties": {
|
|
126
|
+
"timeout": { "type": "number", "minimum": 0 },
|
|
127
|
+
"maxRetries": { "type": "integer", "minimum": 0 },
|
|
128
|
+
"endpoint": { "type": "string" },
|
|
129
|
+
"baseURL": { "type": "string" }
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": false
|
|
132
|
+
},
|
|
133
|
+
"llmOptions": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"description": "Per-call LLM configuration.",
|
|
136
|
+
"properties": {
|
|
137
|
+
"systemPrompt": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"description": "System prompt for the model. Placed vendor-appropriately (top-level for Anthropic/Bedrock, config.systemInstruction for Gemini, role:'system' for OpenAI-compatibles)."
|
|
140
|
+
},
|
|
141
|
+
"messages": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"description": "Seed conversation history. A role:'system' entry is extracted into systemPrompt internally.",
|
|
144
|
+
"items": { "$ref": "#/$defs/llmMessage" }
|
|
145
|
+
},
|
|
146
|
+
"initialMessages": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"description": "Alias of 'messages' (historical).",
|
|
149
|
+
"items": { "$ref": "#/$defs/llmMessage" }
|
|
150
|
+
},
|
|
151
|
+
"maxTokens": {
|
|
152
|
+
"type": "integer",
|
|
153
|
+
"minimum": 1,
|
|
154
|
+
"description": "Maximum tokens the model may generate per turn."
|
|
155
|
+
},
|
|
156
|
+
"temperature": {
|
|
157
|
+
"type": "number",
|
|
158
|
+
"minimum": 0,
|
|
159
|
+
"description": "Sampling temperature."
|
|
160
|
+
},
|
|
161
|
+
"tools": {
|
|
162
|
+
"type": "array",
|
|
163
|
+
"description": "Tool / function definitions available to the model. The MCP-flat shape `{name, description, parameters}` is canonical; the OpenAI-wrapped form `{type:'function', function:{...}}` is also accepted.",
|
|
164
|
+
"items": {
|
|
165
|
+
"type": "object"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"additionalProperties": false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"additionalProperties": false
|
|
91
173
|
},
|
|
92
174
|
"actionHook": {
|
|
93
175
|
"$ref": "../components/actionHook",
|
|
@@ -177,6 +259,21 @@
|
|
|
177
259
|
"required": [
|
|
178
260
|
"llm"
|
|
179
261
|
],
|
|
262
|
+
"$defs": {
|
|
263
|
+
"llmMessage": {
|
|
264
|
+
"type": "object",
|
|
265
|
+
"description": "A conversation-history message. The library normalizes content to a string; adapters may carry vendor-native shapes internally.",
|
|
266
|
+
"required": ["role", "content"],
|
|
267
|
+
"properties": {
|
|
268
|
+
"role": {
|
|
269
|
+
"type": "string",
|
|
270
|
+
"enum": ["system", "user", "assistant", "tool"]
|
|
271
|
+
},
|
|
272
|
+
"content": {}
|
|
273
|
+
},
|
|
274
|
+
"additionalProperties": true
|
|
275
|
+
}
|
|
276
|
+
},
|
|
180
277
|
"examples": [
|
|
181
278
|
{
|
|
182
279
|
"verb": "agent",
|