@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.2.2",
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"
@@ -86,8 +86,90 @@
86
86
  },
87
87
  "llm": {
88
88
  "type": "object",
89
- "description": "LLM configuration for the agent. See the 'llm' verb schema for details.",
90
- "additionalProperties": true
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",