@jambonz/schema 0.2.2 → 0.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.
@@ -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
+ }
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.0",
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,89 @@
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
+ ],
103
+ "description": "LLM vendor id. Must match a `@jambonz/llm` registered adapter."
104
+ },
105
+ "model": {
106
+ "type": "string",
107
+ "description": "Vendor-specific model id (e.g. 'gpt-4o', 'claude-sonnet-4-5-20250929')."
108
+ },
109
+ "label": {
110
+ "type": "string",
111
+ "description": "Optional label to disambiguate when the account has multiple credentials for the same vendor."
112
+ },
113
+ "auth": {
114
+ "type": "object",
115
+ "description": "Optional inline credentials. When omitted, feature-server looks up credentials by (vendor, label) from the database.",
116
+ "properties": {
117
+ "apiKey": { "type": "string" }
118
+ },
119
+ "additionalProperties": true
120
+ },
121
+ "connectOptions": {
122
+ "type": "object",
123
+ "description": "SDK-level client options.",
124
+ "properties": {
125
+ "timeout": { "type": "number", "minimum": 0 },
126
+ "maxRetries": { "type": "integer", "minimum": 0 },
127
+ "endpoint": { "type": "string" },
128
+ "baseURL": { "type": "string" }
129
+ },
130
+ "additionalProperties": false
131
+ },
132
+ "llmOptions": {
133
+ "type": "object",
134
+ "description": "Per-call LLM configuration.",
135
+ "properties": {
136
+ "systemPrompt": {
137
+ "type": "string",
138
+ "description": "System prompt for the model. Placed vendor-appropriately (top-level for Anthropic/Bedrock, config.systemInstruction for Gemini, role:'system' for OpenAI-compatibles)."
139
+ },
140
+ "messages": {
141
+ "type": "array",
142
+ "description": "Seed conversation history. A role:'system' entry is extracted into systemPrompt internally.",
143
+ "items": { "$ref": "#/$defs/llmMessage" }
144
+ },
145
+ "initialMessages": {
146
+ "type": "array",
147
+ "description": "Alias of 'messages' (historical).",
148
+ "items": { "$ref": "#/$defs/llmMessage" }
149
+ },
150
+ "maxTokens": {
151
+ "type": "integer",
152
+ "minimum": 1,
153
+ "description": "Maximum tokens the model may generate per turn."
154
+ },
155
+ "temperature": {
156
+ "type": "number",
157
+ "minimum": 0,
158
+ "description": "Sampling temperature."
159
+ },
160
+ "tools": {
161
+ "type": "array",
162
+ "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.",
163
+ "items": {
164
+ "type": "object"
165
+ }
166
+ }
167
+ },
168
+ "additionalProperties": false
169
+ }
170
+ },
171
+ "additionalProperties": false
91
172
  },
92
173
  "actionHook": {
93
174
  "$ref": "../components/actionHook",
@@ -177,6 +258,21 @@
177
258
  "required": [
178
259
  "llm"
179
260
  ],
261
+ "$defs": {
262
+ "llmMessage": {
263
+ "type": "object",
264
+ "description": "A conversation-history message. The library normalizes content to a string; adapters may carry vendor-native shapes internally.",
265
+ "required": ["role", "content"],
266
+ "properties": {
267
+ "role": {
268
+ "type": "string",
269
+ "enum": ["system", "user", "assistant", "tool"]
270
+ },
271
+ "content": {}
272
+ },
273
+ "additionalProperties": true
274
+ }
275
+ },
180
276
  "examples": [
181
277
  {
182
278
  "verb": "agent",