audrey 0.14.0 → 0.16.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/src/llm.js CHANGED
@@ -1,240 +1,246 @@
1
- /**
2
- * @typedef {Object} ChatMessage
3
- * @property {'system' | 'user' | 'assistant'} role
4
- * @property {string} content
5
- */
6
-
7
- /**
8
- * @typedef {Object} LLMCompletionResult
9
- * @property {string} content
10
- */
11
-
12
- /**
13
- * @typedef {Object} LLMCompletionOptions
14
- * @property {number} [maxTokens]
15
- */
16
-
17
- /**
18
- * @typedef {Object} LLMProvider
19
- * @property {string} modelName
20
- * @property {string} modelVersion
21
- * @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<LLMCompletionResult>} complete
22
- * @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<Object>} json
23
- */
24
-
25
- /**
26
- * @typedef {Object} MockLLMConfig
27
- * @property {'mock'} provider
28
- * @property {Record<string, Object>} [responses={}]
29
- */
30
-
31
- /**
32
- * @typedef {Object} AnthropicLLMConfig
33
- * @property {'anthropic'} provider
34
- * @property {string} [apiKey]
35
- * @property {string} [model='claude-sonnet-4-6']
36
- * @property {number} [maxTokens=1024]
37
- */
38
-
39
- /**
40
- * @typedef {Object} OpenAILLMConfig
41
- * @property {'openai'} provider
42
- * @property {string} [apiKey]
43
- * @property {string} [model='gpt-4o']
44
- * @property {number} [maxTokens=1024]
45
- */
46
-
47
- const PROMPT_TYPE_KEYS = [
48
- 'principleExtraction',
49
- 'contradictionDetection',
50
- 'causalArticulation',
51
- 'contextResolution',
52
- ];
53
-
54
- /** @implements {LLMProvider} */
55
- export class MockLLMProvider {
56
- /** @param {Partial<MockLLMConfig>} [config={}] */
57
- constructor({ responses = {} } = {}) {
58
- this.responses = responses;
59
- this.modelName = 'mock-llm';
60
- this.modelVersion = '1.0.0';
61
- }
62
-
63
- _matchPromptType(messages) {
64
- const systemMsg = messages.find(m => m.role === 'system')?.content || '';
65
- for (const key of PROMPT_TYPE_KEYS) {
66
- if (systemMsg.includes(key)) return key;
67
- }
68
- return null;
69
- }
70
-
71
- /**
72
- * @param {ChatMessage[]} messages
73
- * @returns {Promise<LLMCompletionResult>}
74
- */
75
- async complete(messages) {
76
- const promptType = this._matchPromptType(messages);
77
- const cannedResponse = promptType ? this.responses[promptType] : undefined;
78
- return { content: cannedResponse !== undefined ? JSON.stringify(cannedResponse) : '{}' };
79
- }
80
-
81
- /**
82
- * @param {ChatMessage[]} messages
83
- * @returns {Promise<Object>}
84
- */
85
- async json(messages) {
86
- const promptType = this._matchPromptType(messages);
87
- const cannedResponse = promptType ? this.responses[promptType] : undefined;
88
- return cannedResponse !== undefined ? cannedResponse : {};
89
- }
90
- }
91
-
92
- /** @implements {LLMProvider} */
93
- export class AnthropicLLMProvider {
94
- /** @param {Partial<AnthropicLLMConfig>} [config={}] */
95
- constructor({ apiKey, model = 'claude-sonnet-4-6', maxTokens = 1024, timeout = 30000 } = {}) {
96
- this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
97
- this.model = model;
98
- this.maxTokens = maxTokens;
99
- this.timeout = timeout;
100
- this.modelName = model;
101
- this.modelVersion = 'latest';
102
- }
103
-
104
- /**
105
- * @param {ChatMessage[]} messages
106
- * @param {LLMCompletionOptions} [options={}]
107
- * @returns {Promise<LLMCompletionResult>}
108
- */
109
- async complete(messages, options = {}) {
110
- const systemMsg = messages.find(m => m.role === 'system')?.content;
111
- const nonSystemMsgs = messages.filter(m => m.role !== 'system');
112
-
113
- const body = {
114
- model: this.model,
115
- max_tokens: options.maxTokens || this.maxTokens,
116
- messages: nonSystemMsgs,
117
- };
118
- if (systemMsg) body.system = systemMsg;
119
-
120
- const controller = new AbortController();
121
- const timer = setTimeout(() => controller.abort(), this.timeout);
122
- try {
123
- const response = await fetch('https://api.anthropic.com/v1/messages', {
124
- method: 'POST',
125
- headers: {
126
- 'x-api-key': this.apiKey,
127
- 'anthropic-version': '2023-06-01',
128
- 'content-type': 'application/json',
129
- },
130
- body: JSON.stringify(body),
131
- signal: controller.signal,
132
- });
133
-
134
- if (!response.ok) {
135
- throw new Error(`Anthropic API error: ${response.status}`);
136
- }
137
-
138
- const data = await response.json();
139
- const text = data.content?.[0]?.text || '';
140
- return { content: text };
141
- } finally {
142
- clearTimeout(timer);
143
- }
144
- }
145
-
146
- /**
147
- * @param {ChatMessage[]} messages
148
- * @param {LLMCompletionOptions} [options={}]
149
- * @returns {Promise<Object>}
150
- */
151
- async json(messages, options = {}) {
152
- const result = await this.complete(messages, options);
153
- try {
154
- return JSON.parse(result.content);
155
- } catch {
156
- throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
157
- }
158
- }
159
- }
160
-
161
- /** @implements {LLMProvider} */
162
- export class OpenAILLMProvider {
163
- /** @param {Partial<OpenAILLMConfig>} [config={}] */
164
- constructor({ apiKey, model = 'gpt-4o', maxTokens = 1024, timeout = 30000 } = {}) {
165
- this.apiKey = apiKey || process.env.OPENAI_API_KEY;
166
- this.model = model;
167
- this.maxTokens = maxTokens;
168
- this.timeout = timeout;
169
- this.modelName = model;
170
- this.modelVersion = 'latest';
171
- }
172
-
173
- /**
174
- * @param {ChatMessage[]} messages
175
- * @param {LLMCompletionOptions} [options={}]
176
- * @returns {Promise<LLMCompletionResult>}
177
- */
178
- async complete(messages, options = {}) {
179
- const body = {
180
- model: this.model,
181
- max_tokens: options.maxTokens || this.maxTokens,
182
- messages,
183
- };
184
-
185
- const controller = new AbortController();
186
- const timer = setTimeout(() => controller.abort(), this.timeout);
187
- try {
188
- const response = await fetch('https://api.openai.com/v1/chat/completions', {
189
- method: 'POST',
190
- headers: {
191
- 'Authorization': `Bearer ${this.apiKey}`,
192
- 'Content-Type': 'application/json',
193
- },
194
- body: JSON.stringify(body),
195
- signal: controller.signal,
196
- });
197
-
198
- if (!response.ok) {
199
- throw new Error(`OpenAI API error: ${response.status}`);
200
- }
201
-
202
- const data = await response.json();
203
- const text = data.choices?.[0]?.message?.content || '';
204
- return { content: text };
205
- } finally {
206
- clearTimeout(timer);
207
- }
208
- }
209
-
210
- /**
211
- * @param {ChatMessage[]} messages
212
- * @param {LLMCompletionOptions} [options={}]
213
- * @returns {Promise<Object>}
214
- */
215
- async json(messages, options = {}) {
216
- const result = await this.complete(messages, options);
217
- try {
218
- return JSON.parse(result.content);
219
- } catch {
220
- throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
221
- }
222
- }
223
- }
224
-
225
- /**
226
- * @param {MockLLMConfig | AnthropicLLMConfig | OpenAILLMConfig} config
227
- * @returns {MockLLMProvider | AnthropicLLMProvider | OpenAILLMProvider}
228
- */
229
- export function createLLMProvider(config) {
230
- switch (config.provider) {
231
- case 'mock':
232
- return new MockLLMProvider(config);
233
- case 'anthropic':
234
- return new AnthropicLLMProvider(config);
235
- case 'openai':
236
- return new OpenAILLMProvider(config);
237
- default:
238
- throw new Error(`Unknown LLM provider: ${config.provider}. Valid: mock, anthropic, openai`);
239
- }
240
- }
1
+ /**
2
+ * @typedef {Object} ChatMessage
3
+ * @property {'system' | 'user' | 'assistant'} role
4
+ * @property {string} content
5
+ */
6
+
7
+ function extractJSON(text) {
8
+ const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
9
+ return fenced ? fenced[1].trim() : text.trim();
10
+ }
11
+
12
+ /**
13
+ * @typedef {Object} LLMCompletionResult
14
+ * @property {string} content
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} LLMCompletionOptions
19
+ * @property {number} [maxTokens]
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} LLMProvider
24
+ * @property {string} modelName
25
+ * @property {string} modelVersion
26
+ * @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<LLMCompletionResult>} complete
27
+ * @property {(messages: ChatMessage[], options?: LLMCompletionOptions) => Promise<Object>} json
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} MockLLMConfig
32
+ * @property {'mock'} provider
33
+ * @property {Record<string, Object>} [responses={}]
34
+ */
35
+
36
+ /**
37
+ * @typedef {Object} AnthropicLLMConfig
38
+ * @property {'anthropic'} provider
39
+ * @property {string} [apiKey]
40
+ * @property {string} [model='claude-sonnet-4-6']
41
+ * @property {number} [maxTokens=1024]
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} OpenAILLMConfig
46
+ * @property {'openai'} provider
47
+ * @property {string} [apiKey]
48
+ * @property {string} [model='gpt-4o']
49
+ * @property {number} [maxTokens=1024]
50
+ */
51
+
52
+ const PROMPT_TYPE_KEYS = [
53
+ 'principleExtraction',
54
+ 'contradictionDetection',
55
+ 'causalArticulation',
56
+ 'contextResolution',
57
+ ];
58
+
59
+ /** @implements {LLMProvider} */
60
+ export class MockLLMProvider {
61
+ /** @param {Partial<MockLLMConfig>} [config={}] */
62
+ constructor({ responses = {} } = {}) {
63
+ this.responses = responses;
64
+ this.modelName = 'mock-llm';
65
+ this.modelVersion = '1.0.0';
66
+ }
67
+
68
+ _matchPromptType(messages) {
69
+ const systemMsg = messages.find(m => m.role === 'system')?.content || '';
70
+ for (const key of PROMPT_TYPE_KEYS) {
71
+ if (systemMsg.includes(key)) return key;
72
+ }
73
+ return null;
74
+ }
75
+
76
+ /**
77
+ * @param {ChatMessage[]} messages
78
+ * @returns {Promise<LLMCompletionResult>}
79
+ */
80
+ async complete(messages) {
81
+ const promptType = this._matchPromptType(messages);
82
+ const cannedResponse = promptType ? this.responses[promptType] : undefined;
83
+ return { content: cannedResponse !== undefined ? JSON.stringify(cannedResponse) : '{}' };
84
+ }
85
+
86
+ /**
87
+ * @param {ChatMessage[]} messages
88
+ * @returns {Promise<Object>}
89
+ */
90
+ async json(messages) {
91
+ const promptType = this._matchPromptType(messages);
92
+ const cannedResponse = promptType ? this.responses[promptType] : undefined;
93
+ return cannedResponse !== undefined ? cannedResponse : {};
94
+ }
95
+ }
96
+
97
+ /** @implements {LLMProvider} */
98
+ export class AnthropicLLMProvider {
99
+ /** @param {Partial<AnthropicLLMConfig>} [config={}] */
100
+ constructor({ apiKey, model = 'claude-sonnet-4-6', maxTokens = 1024, timeout = 30000 } = {}) {
101
+ this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
102
+ this.model = model;
103
+ this.maxTokens = maxTokens;
104
+ this.timeout = timeout;
105
+ this.modelName = model;
106
+ this.modelVersion = 'latest';
107
+ }
108
+
109
+ /**
110
+ * @param {ChatMessage[]} messages
111
+ * @param {LLMCompletionOptions} [options={}]
112
+ * @returns {Promise<LLMCompletionResult>}
113
+ */
114
+ async complete(messages, options = {}) {
115
+ const systemMsg = messages.find(m => m.role === 'system')?.content;
116
+ const nonSystemMsgs = messages.filter(m => m.role !== 'system');
117
+
118
+ const body = {
119
+ model: this.model,
120
+ max_tokens: options.maxTokens || this.maxTokens,
121
+ messages: nonSystemMsgs,
122
+ };
123
+ if (systemMsg) body.system = systemMsg;
124
+
125
+ const controller = new AbortController();
126
+ const timer = setTimeout(() => controller.abort(), this.timeout);
127
+ try {
128
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
129
+ method: 'POST',
130
+ headers: {
131
+ 'x-api-key': this.apiKey,
132
+ 'anthropic-version': '2023-06-01',
133
+ 'content-type': 'application/json',
134
+ },
135
+ body: JSON.stringify(body),
136
+ signal: controller.signal,
137
+ });
138
+
139
+ if (!response.ok) {
140
+ const errorBody = await response.text().catch(() => '');
141
+ throw new Error(`Anthropic API error: ${response.status} ${errorBody}`);
142
+ }
143
+
144
+ const data = await response.json();
145
+ const text = data.content?.[0]?.text || '';
146
+ return { content: text };
147
+ } finally {
148
+ clearTimeout(timer);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @param {ChatMessage[]} messages
154
+ * @param {LLMCompletionOptions} [options={}]
155
+ * @returns {Promise<Object>}
156
+ */
157
+ async json(messages, options = {}) {
158
+ const result = await this.complete(messages, options);
159
+ try {
160
+ return JSON.parse(extractJSON(result.content));
161
+ } catch {
162
+ throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
163
+ }
164
+ }
165
+ }
166
+
167
+ /** @implements {LLMProvider} */
168
+ export class OpenAILLMProvider {
169
+ /** @param {Partial<OpenAILLMConfig>} [config={}] */
170
+ constructor({ apiKey, model = 'gpt-4o', maxTokens = 1024, timeout = 30000 } = {}) {
171
+ this.apiKey = apiKey || process.env.OPENAI_API_KEY;
172
+ this.model = model;
173
+ this.maxTokens = maxTokens;
174
+ this.timeout = timeout;
175
+ this.modelName = model;
176
+ this.modelVersion = 'latest';
177
+ }
178
+
179
+ /**
180
+ * @param {ChatMessage[]} messages
181
+ * @param {LLMCompletionOptions} [options={}]
182
+ * @returns {Promise<LLMCompletionResult>}
183
+ */
184
+ async complete(messages, options = {}) {
185
+ const body = {
186
+ model: this.model,
187
+ max_tokens: options.maxTokens || this.maxTokens,
188
+ messages,
189
+ };
190
+
191
+ const controller = new AbortController();
192
+ const timer = setTimeout(() => controller.abort(), this.timeout);
193
+ try {
194
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Authorization': `Bearer ${this.apiKey}`,
198
+ 'Content-Type': 'application/json',
199
+ },
200
+ body: JSON.stringify(body),
201
+ signal: controller.signal,
202
+ });
203
+
204
+ if (!response.ok) {
205
+ throw new Error(`OpenAI API error: ${response.status}`);
206
+ }
207
+
208
+ const data = await response.json();
209
+ const text = data.choices?.[0]?.message?.content || '';
210
+ return { content: text };
211
+ } finally {
212
+ clearTimeout(timer);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * @param {ChatMessage[]} messages
218
+ * @param {LLMCompletionOptions} [options={}]
219
+ * @returns {Promise<Object>}
220
+ */
221
+ async json(messages, options = {}) {
222
+ const result = await this.complete(messages, options);
223
+ try {
224
+ return JSON.parse(extractJSON(result.content));
225
+ } catch {
226
+ throw new Error(`Failed to parse LLM response as JSON: ${result.content.slice(0, 200)}`);
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * @param {MockLLMConfig | AnthropicLLMConfig | OpenAILLMConfig} config
233
+ * @returns {MockLLMProvider | AnthropicLLMProvider | OpenAILLMProvider}
234
+ */
235
+ export function createLLMProvider(config) {
236
+ switch (config.provider) {
237
+ case 'mock':
238
+ return new MockLLMProvider(config);
239
+ case 'anthropic':
240
+ return new AnthropicLLMProvider(config);
241
+ case 'openai':
242
+ return new OpenAILLMProvider(config);
243
+ default:
244
+ throw new Error(`Unknown LLM provider: ${config.provider}. Valid: mock, anthropic, openai`);
245
+ }
246
+ }
package/src/migrate.js CHANGED
@@ -1,58 +1,58 @@
1
- import { dropVec0Tables, createVec0Tables } from './db.js';
2
-
3
- export async function reembedAll(db, embeddingProvider, { dropAndRecreate = false } = {}) {
4
- if (dropAndRecreate) {
5
- dropVec0Tables(db);
6
- createVec0Tables(db, embeddingProvider.dimensions);
7
- }
8
-
9
- const episodes = db.prepare('SELECT id, content, source FROM episodes').all();
10
- const semantics = db.prepare('SELECT id, content, state FROM semantics').all();
11
- const procedures = db.prepare('SELECT id, content, state FROM procedures').all();
12
-
13
- const episodeVectors = episodes.length > 0
14
- ? await embeddingProvider.embedBatch(episodes.map(ep => ep.content))
15
- : [];
16
- const semanticVectors = semantics.length > 0
17
- ? await embeddingProvider.embedBatch(semantics.map(s => s.content))
18
- : [];
19
- const procedureVectors = procedures.length > 0
20
- ? await embeddingProvider.embedBatch(procedures.map(p => p.content))
21
- : [];
22
-
23
- const updateEpLegacy = db.prepare('UPDATE episodes SET embedding = ? WHERE id = ?');
24
- const deleteVecEp = db.prepare('DELETE FROM vec_episodes WHERE id = ?');
25
- const insertVecEp = db.prepare('INSERT INTO vec_episodes(id, embedding, source, consolidated) VALUES (?, ?, ?, ?)');
26
-
27
- const updateSemLegacy = db.prepare('UPDATE semantics SET embedding = ? WHERE id = ?');
28
- const deleteVecSem = db.prepare('DELETE FROM vec_semantics WHERE id = ?');
29
- const insertVecSem = db.prepare('INSERT INTO vec_semantics(id, embedding, state) VALUES (?, ?, ?)');
30
-
31
- const updateProcLegacy = db.prepare('UPDATE procedures SET embedding = ? WHERE id = ?');
32
- const deleteVecProc = db.prepare('DELETE FROM vec_procedures WHERE id = ?');
33
- const insertVecProc = db.prepare('INSERT INTO vec_procedures(id, embedding, state) VALUES (?, ?, ?)');
34
-
35
- const writeTx = db.transaction(() => {
36
- for (let i = 0; i < episodes.length; i++) {
37
- const buf = embeddingProvider.vectorToBuffer(episodeVectors[i]);
38
- updateEpLegacy.run(buf, episodes[i].id);
39
- deleteVecEp.run(episodes[i].id);
40
- insertVecEp.run(episodes[i].id, buf, episodes[i].source, BigInt(0));
41
- }
42
- for (let i = 0; i < semantics.length; i++) {
43
- const buf = embeddingProvider.vectorToBuffer(semanticVectors[i]);
44
- updateSemLegacy.run(buf, semantics[i].id);
45
- deleteVecSem.run(semantics[i].id);
46
- insertVecSem.run(semantics[i].id, buf, semantics[i].state);
47
- }
48
- for (let i = 0; i < procedures.length; i++) {
49
- const buf = embeddingProvider.vectorToBuffer(procedureVectors[i]);
50
- updateProcLegacy.run(buf, procedures[i].id);
51
- deleteVecProc.run(procedures[i].id);
52
- insertVecProc.run(procedures[i].id, buf, procedures[i].state);
53
- }
54
- });
55
- writeTx();
56
-
57
- return { episodes: episodes.length, semantics: semantics.length, procedures: procedures.length };
58
- }
1
+ import { dropVec0Tables, createVec0Tables } from './db.js';
2
+
3
+ export async function reembedAll(db, embeddingProvider, { dropAndRecreate = false } = {}) {
4
+ if (dropAndRecreate) {
5
+ dropVec0Tables(db);
6
+ createVec0Tables(db, embeddingProvider.dimensions);
7
+ }
8
+
9
+ const episodes = db.prepare('SELECT id, content, source FROM episodes').all();
10
+ const semantics = db.prepare('SELECT id, content, state FROM semantics').all();
11
+ const procedures = db.prepare('SELECT id, content, state FROM procedures').all();
12
+
13
+ const episodeVectors = episodes.length > 0
14
+ ? await embeddingProvider.embedBatch(episodes.map(ep => ep.content))
15
+ : [];
16
+ const semanticVectors = semantics.length > 0
17
+ ? await embeddingProvider.embedBatch(semantics.map(s => s.content))
18
+ : [];
19
+ const procedureVectors = procedures.length > 0
20
+ ? await embeddingProvider.embedBatch(procedures.map(p => p.content))
21
+ : [];
22
+
23
+ const updateEpLegacy = db.prepare('UPDATE episodes SET embedding = ? WHERE id = ?');
24
+ const deleteVecEp = db.prepare('DELETE FROM vec_episodes WHERE id = ?');
25
+ const insertVecEp = db.prepare('INSERT INTO vec_episodes(id, embedding, source, consolidated) VALUES (?, ?, ?, ?)');
26
+
27
+ const updateSemLegacy = db.prepare('UPDATE semantics SET embedding = ? WHERE id = ?');
28
+ const deleteVecSem = db.prepare('DELETE FROM vec_semantics WHERE id = ?');
29
+ const insertVecSem = db.prepare('INSERT INTO vec_semantics(id, embedding, state) VALUES (?, ?, ?)');
30
+
31
+ const updateProcLegacy = db.prepare('UPDATE procedures SET embedding = ? WHERE id = ?');
32
+ const deleteVecProc = db.prepare('DELETE FROM vec_procedures WHERE id = ?');
33
+ const insertVecProc = db.prepare('INSERT INTO vec_procedures(id, embedding, state) VALUES (?, ?, ?)');
34
+
35
+ const writeTx = db.transaction(() => {
36
+ for (let i = 0; i < episodes.length; i++) {
37
+ const buf = embeddingProvider.vectorToBuffer(episodeVectors[i]);
38
+ updateEpLegacy.run(buf, episodes[i].id);
39
+ deleteVecEp.run(episodes[i].id);
40
+ insertVecEp.run(episodes[i].id, buf, episodes[i].source, BigInt(0));
41
+ }
42
+ for (let i = 0; i < semantics.length; i++) {
43
+ const buf = embeddingProvider.vectorToBuffer(semanticVectors[i]);
44
+ updateSemLegacy.run(buf, semantics[i].id);
45
+ deleteVecSem.run(semantics[i].id);
46
+ insertVecSem.run(semantics[i].id, buf, semantics[i].state);
47
+ }
48
+ for (let i = 0; i < procedures.length; i++) {
49
+ const buf = embeddingProvider.vectorToBuffer(procedureVectors[i]);
50
+ updateProcLegacy.run(buf, procedures[i].id);
51
+ deleteVecProc.run(procedures[i].id);
52
+ insertVecProc.run(procedures[i].id, buf, procedures[i].state);
53
+ }
54
+ });
55
+ writeTx();
56
+
57
+ return { episodes: episodes.length, semantics: semantics.length, procedures: procedures.length };
58
+ }