@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.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/AGENTSAM.md +55 -0
- package/README.md +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/RELEASES.md +16 -7
- package/package.json +20 -8
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/catalog.json +18 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -0
- package/src/cli.js +98 -28
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/deploy.js +39 -3
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/models.js +85 -40
- package/src/commands/preferences.js +101 -59
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +370 -109
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +55 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/cli-preferences.js +28 -24
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -16
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/process.js +35 -9
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +1 -0
- package/src/ui/boot.js +15 -17
- package/test/account-session.test.mjs +36 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +30 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +4 -4
- package/test/responses-runner.test.mjs +148 -0
- package/test/shell.test.mjs +47 -20
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { calculateModelCost, getModelRecord } from '../models/index.js';
|
|
2
|
+
import { createAgentEvent, createUsageSnapshot } from '../telemetry/index.js';
|
|
3
|
+
import { createOpenAIHttpError, diagnosticFromError } from '../errors/index.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
6
|
+
|
|
7
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
8
|
+
function integer(value) { const n = Number(value ?? 0); return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; }
|
|
9
|
+
|
|
10
|
+
function normalizeServiceTier(value, requested = 'default') {
|
|
11
|
+
const tier = clean(value).toLowerCase();
|
|
12
|
+
if (tier === 'priority') return 'fast';
|
|
13
|
+
if (tier === 'auto' || !tier) return requested === 'auto' ? 'default' : requested;
|
|
14
|
+
return tier;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function extractOpenAIOutputText(response = {}) {
|
|
18
|
+
if (typeof response.output_text === 'string') return response.output_text;
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for (const item of response.output || []) {
|
|
21
|
+
if (item?.type !== 'message') continue;
|
|
22
|
+
for (const content of item.content || []) {
|
|
23
|
+
if (content?.type === 'output_text' && typeof content.text === 'string') chunks.push(content.text);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return chunks.join('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function extractOpenAIFunctionCalls(response = {}) {
|
|
30
|
+
return (response.output || []).filter((item) => item?.type === 'function_call').map((item) => Object.freeze({
|
|
31
|
+
id: item.id || null,
|
|
32
|
+
call_id: item.call_id,
|
|
33
|
+
name: item.name,
|
|
34
|
+
arguments: item.arguments || '{}',
|
|
35
|
+
status: item.status || null,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function usageParts(response = {}) {
|
|
40
|
+
const usage = response.usage || {};
|
|
41
|
+
return {
|
|
42
|
+
input_tokens: integer(usage.input_tokens),
|
|
43
|
+
cached_input_tokens: integer(usage.input_tokens_details?.cached_tokens),
|
|
44
|
+
cache_write_tokens: integer(usage.input_tokens_details?.cache_write_tokens),
|
|
45
|
+
output_tokens: integer(usage.output_tokens),
|
|
46
|
+
reasoning_tokens: integer(usage.output_tokens_details?.reasoning_tokens),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function errorMessage(body, status) {
|
|
51
|
+
return body?.error?.message || body?.message || `OpenAI Responses API returned HTTP ${status}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function optionalSignal(timeoutMs) {
|
|
55
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return undefined;
|
|
56
|
+
if (typeof AbortSignal?.timeout !== 'function') return undefined;
|
|
57
|
+
return AbortSignal.timeout(Math.floor(timeoutMs));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeTools(tools = []) {
|
|
61
|
+
if (!Array.isArray(tools)) throw new TypeError('tools must be an array');
|
|
62
|
+
return tools.map((tool) => {
|
|
63
|
+
if (tool?.type !== 'function' || !clean(tool.name)) throw new TypeError('OpenAI adapter tools must be Responses function tool descriptors');
|
|
64
|
+
return {
|
|
65
|
+
type: 'function',
|
|
66
|
+
name: clean(tool.name),
|
|
67
|
+
description: clean(tool.description) || undefined,
|
|
68
|
+
parameters: tool.parameters || { type: 'object', properties: {} },
|
|
69
|
+
strict: tool.strict !== false,
|
|
70
|
+
...(tool.async === true ? { async: true } : {}),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertRuntimeConfig(model, reasoningEffort, serviceTier) {
|
|
76
|
+
const record = getModelRecord(model);
|
|
77
|
+
if (!record || record.provider !== 'openai') throw new RangeError(`unsupported OpenAI model catalog entry: ${model}`);
|
|
78
|
+
if (!record.reasoning_efforts.includes(reasoningEffort)) throw new RangeError(`unsupported reasoning effort for ${record.provider_model_id}: ${reasoningEffort}`);
|
|
79
|
+
if (!record.service_tiers.includes(serviceTier)) throw new RangeError(`unsupported service tier for ${record.provider_model_id}: ${serviceTier}`);
|
|
80
|
+
return record;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function emitEvent(emit, type, payload, meta = {}) {
|
|
84
|
+
if (typeof emit !== 'function') return;
|
|
85
|
+
emit(createAgentEvent(type, payload, meta));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createOpenAIResponsesAdapter(options = {}) {
|
|
89
|
+
const apiKey = clean(options.apiKey || process.env.OPENAI_API_KEY);
|
|
90
|
+
const baseUrl = clean(options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
91
|
+
const fetchImpl = options.fetchImpl || fetch;
|
|
92
|
+
const defaultEmit = options.emit;
|
|
93
|
+
const timeoutMs = options.timeoutMs;
|
|
94
|
+
|
|
95
|
+
async function request(pathname, body, runtime = {}) {
|
|
96
|
+
if (!apiKey) throw new Error('OPENAI_API_KEY is required for OpenAI Responses execution');
|
|
97
|
+
const response = await fetchImpl(`${baseUrl}${pathname}`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
signal: optionalSignal(runtime.timeoutMs ?? timeoutMs),
|
|
102
|
+
});
|
|
103
|
+
let rawText = '';
|
|
104
|
+
let parsed = null;
|
|
105
|
+
if (typeof response.text === 'function') {
|
|
106
|
+
try { rawText = await response.text(); } catch { rawText = ''; }
|
|
107
|
+
if (rawText) {
|
|
108
|
+
try { parsed = JSON.parse(rawText); } catch { parsed = null; }
|
|
109
|
+
}
|
|
110
|
+
} else if (typeof response.json === 'function') {
|
|
111
|
+
try {
|
|
112
|
+
parsed = await response.json();
|
|
113
|
+
rawText = JSON.stringify(parsed ?? null);
|
|
114
|
+
} catch {
|
|
115
|
+
parsed = null;
|
|
116
|
+
rawText = '';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw createOpenAIHttpError({
|
|
121
|
+
status: response.status,
|
|
122
|
+
body: parsed,
|
|
123
|
+
rawText,
|
|
124
|
+
headers: response.headers,
|
|
125
|
+
requestedServiceTier: body?.service_tier,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
data: parsed ?? {},
|
|
130
|
+
http: Object.freeze({
|
|
131
|
+
status: response.status,
|
|
132
|
+
request_id: clean(response.headers?.get?.('x-request-id') || response.headers?.get?.('openai-request-id')) || null,
|
|
133
|
+
ray_id: clean(response.headers?.get?.('cf-ray')) || null,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function create(params = {}) {
|
|
139
|
+
const modelRecord = getModelRecord(params.model);
|
|
140
|
+
const model = modelRecord?.provider_model_id || clean(params.model);
|
|
141
|
+
const reasoningEffort = clean(params.reasoningEffort || params.reasoning_effort || 'low');
|
|
142
|
+
const serviceTier = clean(params.serviceTier || params.service_tier || 'default');
|
|
143
|
+
const record = assertRuntimeConfig(model, reasoningEffort, serviceTier);
|
|
144
|
+
const emit = params.emit || defaultEmit;
|
|
145
|
+
const meta = { runId: params.runId, sequence: params.sequence };
|
|
146
|
+
const tools = normalizeTools(params.tools || []);
|
|
147
|
+
const body = {
|
|
148
|
+
model,
|
|
149
|
+
input: params.input ?? '',
|
|
150
|
+
reasoning: { effort: reasoningEffort },
|
|
151
|
+
service_tier: serviceTier,
|
|
152
|
+
store: params.store !== false,
|
|
153
|
+
truncation: 'disabled',
|
|
154
|
+
parallel_tool_calls: params.parallelToolCalls !== false,
|
|
155
|
+
...(clean(params.instructions) ? { instructions: String(params.instructions) } : {}),
|
|
156
|
+
...(tools.length ? { tools } : {}),
|
|
157
|
+
...(clean(params.previousResponseId) ? { previous_response_id: clean(params.previousResponseId) } : {}),
|
|
158
|
+
...(Number.isInteger(params.maxOutputTokens) && params.maxOutputTokens > 0 ? { max_output_tokens: params.maxOutputTokens } : {}),
|
|
159
|
+
...(clean(params.promptCacheKey) ? { prompt_cache_key: clean(params.promptCacheKey) } : {}),
|
|
160
|
+
...(params.promptCacheOptions ? { prompt_cache_options: params.promptCacheOptions } : {}),
|
|
161
|
+
...(params.metadata ? { metadata: params.metadata } : {}),
|
|
162
|
+
...(params.background === true ? { background: true } : {}),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
emitEvent(emit, 'model.started', {
|
|
166
|
+
provider: 'openai', model, reasoning_effort: reasoningEffort, requested_service_tier: serviceTier,
|
|
167
|
+
}, meta);
|
|
168
|
+
|
|
169
|
+
let response;
|
|
170
|
+
let http;
|
|
171
|
+
try {
|
|
172
|
+
const result = await request('/responses', body, params);
|
|
173
|
+
response = result.data;
|
|
174
|
+
http = result.http;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const diagnostic = diagnosticFromError(error, { source: 'openai', kind: 'provider_error' });
|
|
177
|
+
emitEvent(emit, 'error.observed', diagnostic, meta);
|
|
178
|
+
emitEvent(emit, 'run.failed', { stage: 'model', provider: 'openai', model, error: diagnostic }, meta);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const delta = usageParts(response);
|
|
183
|
+
const actualServiceTier = normalizeServiceTier(response.service_tier, serviceTier);
|
|
184
|
+
const cost = calculateModelCost(record, { ...delta, estimate_kind: 'provider' }, { serviceTier: actualServiceTier });
|
|
185
|
+
const usageSnapshot = createUsageSnapshot({
|
|
186
|
+
current_context: { input_tokens: delta.input_tokens, window_tokens: record.context_window },
|
|
187
|
+
cumulative: params.cumulativeUsage ? {
|
|
188
|
+
input_tokens: integer(params.cumulativeUsage.input_tokens) + delta.input_tokens,
|
|
189
|
+
output_tokens: integer(params.cumulativeUsage.output_tokens) + delta.output_tokens,
|
|
190
|
+
cached_input_tokens: integer(params.cumulativeUsage.cached_input_tokens) + delta.cached_input_tokens,
|
|
191
|
+
cache_write_tokens: integer(params.cumulativeUsage.cache_write_tokens) + delta.cache_write_tokens,
|
|
192
|
+
reasoning_tokens: integer(params.cumulativeUsage.reasoning_tokens) + delta.reasoning_tokens,
|
|
193
|
+
} : delta,
|
|
194
|
+
estimate_kind: 'provider',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
emitEvent(emit, 'usage.snapshot', usageSnapshot, meta);
|
|
198
|
+
emitEvent(emit, 'cost.snapshot', cost, meta);
|
|
199
|
+
emitEvent(emit, 'model.completed', {
|
|
200
|
+
provider: 'openai', model, response_id: response.id, status: response.status,
|
|
201
|
+
request_id: http?.request_id || null, ray_id: http?.ray_id || null,
|
|
202
|
+
requested_service_tier: serviceTier, actual_service_tier: actualServiceTier,
|
|
203
|
+
}, meta);
|
|
204
|
+
|
|
205
|
+
return Object.freeze({
|
|
206
|
+
provider: 'openai',
|
|
207
|
+
model,
|
|
208
|
+
response_id: response.id,
|
|
209
|
+
status: response.status,
|
|
210
|
+
request_id: http?.request_id || null,
|
|
211
|
+
ray_id: http?.ray_id || null,
|
|
212
|
+
output_text: extractOpenAIOutputText(response),
|
|
213
|
+
tool_calls: Object.freeze(extractOpenAIFunctionCalls(response)),
|
|
214
|
+
usage_delta: Object.freeze(delta),
|
|
215
|
+
usage_snapshot: usageSnapshot,
|
|
216
|
+
cost,
|
|
217
|
+
requested_service_tier: serviceTier,
|
|
218
|
+
actual_service_tier: actualServiceTier,
|
|
219
|
+
raw: response,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function continueWithToolOutputs(params = {}) {
|
|
224
|
+
const previousResponseId = clean(params.previousResponseId);
|
|
225
|
+
if (!previousResponseId) throw new TypeError('previousResponseId is required');
|
|
226
|
+
const outputs = (params.toolOutputs || []).map((row) => {
|
|
227
|
+
const callId = clean(row.call_id || row.callId);
|
|
228
|
+
if (!callId) throw new TypeError('tool output call_id is required');
|
|
229
|
+
const output = typeof row.output === 'string' ? row.output : JSON.stringify(row.output ?? null);
|
|
230
|
+
return { type: 'function_call_output', call_id: callId, output };
|
|
231
|
+
});
|
|
232
|
+
if (!outputs.length) throw new TypeError('at least one tool output is required');
|
|
233
|
+
return create({ ...params, previousResponseId, input: outputs });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function compact(params = {}) {
|
|
237
|
+
const modelRecord = getModelRecord(params.model);
|
|
238
|
+
const model = modelRecord?.provider_model_id || clean(params.model);
|
|
239
|
+
if (!modelRecord || modelRecord.provider !== 'openai') throw new RangeError(`unsupported OpenAI model catalog entry: ${params.model}`);
|
|
240
|
+
const emit = params.emit || defaultEmit;
|
|
241
|
+
const meta = { runId: params.runId, sequence: params.sequence };
|
|
242
|
+
const body = {
|
|
243
|
+
model,
|
|
244
|
+
...(clean(params.previousResponseId) ? { previous_response_id: clean(params.previousResponseId) } : {}),
|
|
245
|
+
...(params.input != null ? { input: params.input } : {}),
|
|
246
|
+
...(clean(params.instructions) ? { instructions: String(params.instructions) } : {}),
|
|
247
|
+
...(clean(params.promptCacheKey) ? { prompt_cache_key: clean(params.promptCacheKey) } : {}),
|
|
248
|
+
...(params.promptCacheOptions ? { prompt_cache_options: params.promptCacheOptions } : {}),
|
|
249
|
+
};
|
|
250
|
+
emitEvent(emit, 'context.compaction.started', { provider: 'openai', model, previous_response_id: body.previous_response_id || null }, meta);
|
|
251
|
+
let response;
|
|
252
|
+
let http;
|
|
253
|
+
try {
|
|
254
|
+
const result = await request('/responses/compact', body, params);
|
|
255
|
+
response = result.data;
|
|
256
|
+
http = result.http;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const diagnostic = diagnosticFromError(error, { source: 'openai', kind: 'provider_error' });
|
|
259
|
+
emitEvent(emit, 'error.observed', diagnostic, meta);
|
|
260
|
+
emitEvent(emit, 'run.failed', { stage: 'compaction', provider: 'openai', model, error: diagnostic }, meta);
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
emitEvent(emit, 'context.compaction.completed', {
|
|
264
|
+
provider: 'openai', model, compaction_id: response.id, usage: response.usage || null,
|
|
265
|
+
request_id: http?.request_id || null, ray_id: http?.ray_id || null,
|
|
266
|
+
}, meta);
|
|
267
|
+
return Object.freeze({
|
|
268
|
+
provider: 'openai', model, compaction_id: response.id,
|
|
269
|
+
request_id: http?.request_id || null, ray_id: http?.ray_id || null,
|
|
270
|
+
output: Object.freeze(response.output || []), usage: response.usage || null, raw: response,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return Object.freeze({ provider: 'openai', create, continueWithToolOutputs, compact });
|
|
275
|
+
}
|
package/src/security/process.js
CHANGED
|
@@ -1,31 +1,57 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createProcessDiagnosticError } from '../errors/index.js';
|
|
2
3
|
|
|
3
4
|
export function runProcess(command, args, { cwd, timeoutMs = 300_000, signal, env = process.env, maxBytes = 8 * 1024 * 1024 } = {}) {
|
|
4
5
|
return new Promise((resolve, reject) => {
|
|
5
|
-
|
|
6
|
-
const
|
|
6
|
+
const safeArgs = Array.isArray(args) ? args : [];
|
|
7
|
+
const diagnostic = (code, message, extra = {}) => createProcessDiagnosticError({
|
|
8
|
+
code,
|
|
9
|
+
message,
|
|
10
|
+
command,
|
|
11
|
+
args: safeArgs,
|
|
12
|
+
cwd,
|
|
13
|
+
stdout,
|
|
14
|
+
stderr,
|
|
15
|
+
...extra,
|
|
16
|
+
});
|
|
7
17
|
let stdout = '', stderr = '', size = 0, failure, hardKill;
|
|
18
|
+
if (signal?.aborted) return reject(createProcessDiagnosticError({ code: 'process_cancelled', message: 'Command cancelled', command, args: safeArgs, cwd }));
|
|
19
|
+
const child = spawn(command, safeArgs, { cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
8
20
|
function kill(sig) {
|
|
9
21
|
try { process.kill(process.platform === 'win32' ? child.pid : -child.pid, sig); } catch { /* already exited */ }
|
|
10
22
|
}
|
|
11
|
-
function stop(
|
|
23
|
+
function stop(error) {
|
|
12
24
|
if (failure) return;
|
|
13
|
-
failure =
|
|
25
|
+
failure = error;
|
|
14
26
|
kill('SIGTERM');
|
|
15
27
|
hardKill = setTimeout(() => kill('SIGKILL'), 1000);
|
|
16
28
|
}
|
|
17
|
-
const timer = setTimeout(() => stop('Command timed out'), timeoutMs);
|
|
18
|
-
const abort = () => stop('Command cancelled');
|
|
29
|
+
const timer = setTimeout(() => stop(diagnostic('process_timeout', 'Command timed out', { retriable: false })), timeoutMs);
|
|
30
|
+
const abort = () => stop(diagnostic('process_cancelled', 'Command cancelled'));
|
|
19
31
|
signal?.addEventListener('abort', abort, { once: true });
|
|
20
32
|
const receive = (key) => chunk => {
|
|
21
33
|
size += chunk.length;
|
|
22
|
-
if (size > maxBytes) return stop('Command output exceeded
|
|
34
|
+
if (size > maxBytes) return stop(diagnostic('process_output_limit', `Command output exceeded ${maxBytes} bytes`));
|
|
23
35
|
if (key === 'stdout') stdout += chunk.toString(); else stderr += chunk.toString();
|
|
24
36
|
};
|
|
25
37
|
child.stdout.on('data', receive('stdout'));
|
|
26
38
|
child.stderr.on('data', receive('stderr'));
|
|
27
39
|
const cleanup = () => { clearTimeout(timer); clearTimeout(hardKill); signal?.removeEventListener('abort', abort); };
|
|
28
|
-
child.on('error', () => {
|
|
29
|
-
|
|
40
|
+
child.on('error', (cause) => {
|
|
41
|
+
cleanup();
|
|
42
|
+
reject(createProcessDiagnosticError({
|
|
43
|
+
code: cause?.code || 'process_spawn_failed',
|
|
44
|
+
message: cause?.message || 'Cannot start requested command',
|
|
45
|
+
command,
|
|
46
|
+
args: safeArgs,
|
|
47
|
+
cwd,
|
|
48
|
+
cause,
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
child.on('close', (code, closeSignal) => {
|
|
52
|
+
cleanup();
|
|
53
|
+
if (failure) return reject(failure);
|
|
54
|
+
resolve({ code: code ?? 1, signal: closeSignal || null, stdout, stderr });
|
|
55
|
+
});
|
|
30
56
|
});
|
|
31
57
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export const RUNTIME_RECEIPT_SCHEMA_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
const RUN_MODES = new Set(['ask', 'plan', 'agent', 'debug', 'multitask']);
|
|
4
|
+
const RUN_STATUSES = new Set(['queued', 'running', 'completed', 'failed', 'partial', 'cancelled']);
|
|
5
|
+
const APPROVAL_STATUSES = new Set(['pending', 'approved', 'denied', 'expired']);
|
|
6
|
+
const TERMINAL_STATUSES = new Set(['queued', 'running', 'completed', 'failed', 'cancelled', 'unknown']);
|
|
7
|
+
|
|
8
|
+
function clean(value) {
|
|
9
|
+
return value == null ? '' : String(value).trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function required(value, label) {
|
|
13
|
+
const text = clean(value);
|
|
14
|
+
if (!text) throw new TypeError(`${label} is required`);
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function optional(value) {
|
|
19
|
+
const text = clean(value);
|
|
20
|
+
return text || null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nonNegativeInteger(value, fallback = 0) {
|
|
24
|
+
const number = Number(value ?? fallback);
|
|
25
|
+
if (!Number.isFinite(number) || number < 0) throw new RangeError('expected a non-negative number');
|
|
26
|
+
return Math.floor(number);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function optionalNonNegativeInteger(value) {
|
|
30
|
+
if (value == null || value === '') return null;
|
|
31
|
+
return nonNegativeInteger(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function nonNegativeNumber(value, fallback = 0) {
|
|
35
|
+
const number = Number(value ?? fallback);
|
|
36
|
+
if (!Number.isFinite(number) || number < 0) throw new RangeError('expected a non-negative number');
|
|
37
|
+
return number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function canonicalMode(value) {
|
|
41
|
+
const mode = clean(value || 'agent').toLowerCase();
|
|
42
|
+
if (!RUN_MODES.has(mode)) throw new RangeError(`unsupported run mode: ${mode}`);
|
|
43
|
+
return mode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function canonicalRunStatus(value) {
|
|
47
|
+
const status = clean(value || 'queued').toLowerCase();
|
|
48
|
+
if (!RUN_STATUSES.has(status)) throw new RangeError(`unsupported run status: ${status}`);
|
|
49
|
+
return status;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function jsonText(value, fallback) {
|
|
53
|
+
if (value == null || value === '') return JSON.stringify(fallback);
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
JSON.parse(value);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Provider-neutral execution receipt. The authenticated host supplies account_id;
|
|
63
|
+
* tenant/workspace/user aliases are deliberately not part of this contract.
|
|
64
|
+
*/
|
|
65
|
+
export function createRunReceipt(value = {}) {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
|
|
68
|
+
id: required(value.id ?? value.run_id, 'id'),
|
|
69
|
+
account_id: required(value.account_id ?? value.accountId, 'account_id'),
|
|
70
|
+
conversation_id: optional(value.conversation_id ?? value.conversationId),
|
|
71
|
+
external_agent_id: optional(value.external_agent_id ?? value.externalAgentId),
|
|
72
|
+
parent_run_id: optional(value.parent_run_id ?? value.parentRunId),
|
|
73
|
+
source_client: optional(value.source_client ?? value.sourceClient),
|
|
74
|
+
surface: optional(value.surface),
|
|
75
|
+
mode: canonicalMode(value.mode),
|
|
76
|
+
model_key: optional(value.model_key ?? value.modelKey),
|
|
77
|
+
reasoning_effort: optional(value.reasoning_effort ?? value.reasoningEffort),
|
|
78
|
+
requested_service_tier: optional(value.requested_service_tier ?? value.requestedServiceTier),
|
|
79
|
+
actual_service_tier: optional(value.actual_service_tier ?? value.actualServiceTier),
|
|
80
|
+
selected_by: optional(value.selected_by ?? value.selectedBy),
|
|
81
|
+
routing_arm_id: optional(value.routing_arm_id ?? value.routingArmId),
|
|
82
|
+
status: canonicalRunStatus(value.status),
|
|
83
|
+
cancel_requested: value.cancel_requested === true || value.cancel_requested === 1 ? 1 : 0,
|
|
84
|
+
error_code: optional(value.error_code ?? value.errorCode),
|
|
85
|
+
error_message: optional(value.error_message ?? value.errorMessage),
|
|
86
|
+
model_call_count: nonNegativeInteger(value.model_call_count ?? value.modelCallCount),
|
|
87
|
+
tool_call_count: nonNegativeInteger(value.tool_call_count ?? value.toolCallCount),
|
|
88
|
+
input_tokens: nonNegativeInteger(value.input_tokens ?? value.inputTokens),
|
|
89
|
+
cached_input_tokens: nonNegativeInteger(value.cached_input_tokens ?? value.cachedInputTokens),
|
|
90
|
+
output_tokens: nonNegativeInteger(value.output_tokens ?? value.outputTokens),
|
|
91
|
+
reasoning_tokens: nonNegativeInteger(value.reasoning_tokens ?? value.reasoningTokens),
|
|
92
|
+
cost_usd: nonNegativeNumber(value.cost_usd ?? value.costUsd),
|
|
93
|
+
created_at_unix: optionalNonNegativeInteger(value.created_at_unix ?? value.createdAtUnix),
|
|
94
|
+
started_at_unix: optionalNonNegativeInteger(value.started_at_unix ?? value.startedAtUnix),
|
|
95
|
+
completed_at_unix: optionalNonNegativeInteger(value.completed_at_unix ?? value.completedAtUnix),
|
|
96
|
+
updated_at_unix: optionalNonNegativeInteger(value.updated_at_unix ?? value.updatedAtUnix),
|
|
97
|
+
latency_ms: optionalNonNegativeInteger(value.latency_ms ?? value.latencyMs),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One authoritative provider/model call receipt. */
|
|
102
|
+
export function createUsageReceipt(value = {}) {
|
|
103
|
+
const inputTokens = nonNegativeInteger(value.input_tokens ?? value.inputTokens);
|
|
104
|
+
const cachedInputTokens = nonNegativeInteger(value.cached_input_tokens ?? value.cachedInputTokens);
|
|
105
|
+
const cacheWriteTokens = nonNegativeInteger(value.cache_write_tokens ?? value.cacheWriteTokens);
|
|
106
|
+
const outputTokens = nonNegativeInteger(value.output_tokens ?? value.outputTokens);
|
|
107
|
+
const reasoningTokens = nonNegativeInteger(value.reasoning_tokens ?? value.reasoningTokens);
|
|
108
|
+
const totalTokens = value.total_tokens == null && value.totalTokens == null
|
|
109
|
+
? inputTokens + outputTokens
|
|
110
|
+
: nonNegativeInteger(value.total_tokens ?? value.totalTokens);
|
|
111
|
+
|
|
112
|
+
return Object.freeze({
|
|
113
|
+
schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
|
|
114
|
+
id: required(value.id, 'id'),
|
|
115
|
+
account_id: required(value.account_id ?? value.accountId, 'account_id'),
|
|
116
|
+
agent_run_id: optional(value.agent_run_id ?? value.agentRunId),
|
|
117
|
+
conversation_id: optional(value.conversation_id ?? value.conversationId),
|
|
118
|
+
repository_id: optional(value.repository_id ?? value.repositoryId),
|
|
119
|
+
source_client: optional(value.source_client ?? value.sourceClient),
|
|
120
|
+
usage_kind: optional(value.usage_kind ?? value.usageKind) || 'model',
|
|
121
|
+
provider: required(value.provider, 'provider'),
|
|
122
|
+
model_key: required(value.model_key ?? value.modelKey, 'model_key'),
|
|
123
|
+
model_call_index: optionalNonNegativeInteger(value.model_call_index ?? value.modelCallIndex),
|
|
124
|
+
provider_request_id: optional(value.provider_request_id ?? value.providerRequestId),
|
|
125
|
+
requested_service_tier: optional(value.requested_service_tier ?? value.requestedServiceTier),
|
|
126
|
+
actual_service_tier: optional(value.actual_service_tier ?? value.actualServiceTier),
|
|
127
|
+
input_tokens: inputTokens,
|
|
128
|
+
cached_input_tokens: cachedInputTokens,
|
|
129
|
+
cache_write_tokens: cacheWriteTokens,
|
|
130
|
+
output_tokens: outputTokens,
|
|
131
|
+
reasoning_tokens: reasoningTokens,
|
|
132
|
+
total_tokens: totalTokens,
|
|
133
|
+
cost_usd: nonNegativeNumber(value.cost_usd ?? value.costUsd),
|
|
134
|
+
cost_basis: optional(value.cost_basis ?? value.costBasis),
|
|
135
|
+
duration_ms: optionalNonNegativeInteger(value.duration_ms ?? value.durationMs),
|
|
136
|
+
status: optional(value.status) || 'ok',
|
|
137
|
+
error_code: optional(value.error_code ?? value.errorCode),
|
|
138
|
+
ref_table: optional(value.ref_table ?? value.refTable),
|
|
139
|
+
ref_id: optional(value.ref_id ?? value.refId),
|
|
140
|
+
created_at_unix: optionalNonNegativeInteger(value.created_at_unix ?? value.createdAtUnix),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Approval receipt linked to execution/tool/process lineage without ownership aliases. */
|
|
145
|
+
export function createApprovalReceipt(value = {}) {
|
|
146
|
+
const status = clean(value.status || 'pending').toLowerCase();
|
|
147
|
+
if (!APPROVAL_STATUSES.has(status)) throw new RangeError(`unsupported approval status: ${status}`);
|
|
148
|
+
return Object.freeze({
|
|
149
|
+
schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
|
|
150
|
+
id: required(value.id, 'id'),
|
|
151
|
+
account_id: required(value.account_id ?? value.accountId, 'account_id'),
|
|
152
|
+
agent_run_id: optional(value.agent_run_id ?? value.agentRunId),
|
|
153
|
+
tool_call_id: optional(value.tool_call_id ?? value.toolCallId),
|
|
154
|
+
terminal_job_id: optional(value.terminal_job_id ?? value.terminalJobId),
|
|
155
|
+
conversation_id: optional(value.conversation_id ?? value.conversationId),
|
|
156
|
+
capability_key: optional(value.capability_key ?? value.capabilityKey),
|
|
157
|
+
tool_key: optional(value.tool_key ?? value.toolKey),
|
|
158
|
+
action_summary: required(value.action_summary ?? value.actionSummary, 'action_summary'),
|
|
159
|
+
sanitized_input_json: jsonText(value.sanitized_input_json ?? value.sanitizedInput, {}),
|
|
160
|
+
risk_level: optional(value.risk_level ?? value.riskLevel) || 'medium',
|
|
161
|
+
approval_type: optional(value.approval_type ?? value.approvalType) || 'tool',
|
|
162
|
+
status,
|
|
163
|
+
response_json: jsonText(value.response_json ?? value.response, {}),
|
|
164
|
+
approved_by: optional(value.approved_by ?? value.approvedBy),
|
|
165
|
+
created_at: optionalNonNegativeInteger(value.created_at ?? value.createdAt),
|
|
166
|
+
expires_at: optionalNonNegativeInteger(value.expires_at ?? value.expiresAt),
|
|
167
|
+
decided_at: optionalNonNegativeInteger(value.decided_at ?? value.decidedAt),
|
|
168
|
+
metadata_json: jsonText(value.metadata_json ?? value.metadata, {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Thin durable process-control receipt; full terminal transcripts stay elsewhere. */
|
|
173
|
+
export function createTerminalJobReceipt(value = {}) {
|
|
174
|
+
const status = clean(value.status || 'queued').toLowerCase();
|
|
175
|
+
if (!TERMINAL_STATUSES.has(status)) throw new RangeError(`unsupported terminal job status: ${status}`);
|
|
176
|
+
return Object.freeze({
|
|
177
|
+
schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
|
|
178
|
+
id: required(value.id, 'id'),
|
|
179
|
+
account_id: required(value.account_id ?? value.accountId, 'account_id'),
|
|
180
|
+
instance_id: required(value.instance_id ?? value.instanceId, 'instance_id'),
|
|
181
|
+
connection_id: required(value.connection_id ?? value.connectionId, 'connection_id'),
|
|
182
|
+
session_id: optional(value.session_id ?? value.sessionId),
|
|
183
|
+
source_run_id: optional(value.source_run_id ?? value.sourceRunId),
|
|
184
|
+
tool_call_id: optional(value.tool_call_id ?? value.toolCallId),
|
|
185
|
+
execos_run_id: optional(value.execos_run_id ?? value.execosRunId),
|
|
186
|
+
status,
|
|
187
|
+
cwd: optional(value.cwd),
|
|
188
|
+
timeout_ms: optionalNonNegativeInteger(value.timeout_ms ?? value.timeoutMs),
|
|
189
|
+
exit_code: value.exit_code == null && value.exitCode == null ? null : Number(value.exit_code ?? value.exitCode),
|
|
190
|
+
failure_code: optional(value.failure_code ?? value.failureCode),
|
|
191
|
+
log_ref: optional(value.log_ref ?? value.logRef),
|
|
192
|
+
output_artifact_ref: optional(value.output_artifact_ref ?? value.outputArtifactRef),
|
|
193
|
+
artifact_refs_json: jsonText(value.artifact_refs_json ?? value.artifactRefs, []),
|
|
194
|
+
idempotency_key: optional(value.idempotency_key ?? value.idempotencyKey),
|
|
195
|
+
attempt: nonNegativeInteger(value.attempt),
|
|
196
|
+
max_attempts: Math.max(1, nonNegativeInteger(value.max_attempts ?? value.maxAttempts, 1)),
|
|
197
|
+
last_observed_at: optionalNonNegativeInteger(value.last_observed_at ?? value.lastObservedAt),
|
|
198
|
+
created_at: optionalNonNegativeInteger(value.created_at ?? value.createdAt),
|
|
199
|
+
started_at: optionalNonNegativeInteger(value.started_at ?? value.startedAt),
|
|
200
|
+
finished_at: optionalNonNegativeInteger(value.finished_at ?? value.finishedAt),
|
|
201
|
+
updated_at: optionalNonNegativeInteger(value.updated_at ?? value.updatedAt),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const AGENT_EVENT_TYPES = Object.freeze([
|
|
2
|
+
'run.started', 'run.status', 'run.completed', 'run.failed',
|
|
3
|
+
'error.observed',
|
|
4
|
+
'model.started', 'model.delta', 'model.completed',
|
|
5
|
+
'usage.snapshot', 'cost.snapshot',
|
|
6
|
+
'context.snapshot', 'context.compaction.started', 'context.compaction.completed',
|
|
7
|
+
'tool.search', 'tool.started', 'tool.completed', 'tool.failed',
|
|
8
|
+
'task.updated',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
function integer(value, label) {
|
|
12
|
+
const number = Number(value ?? 0);
|
|
13
|
+
if (!Number.isFinite(number) || number < 0) throw new RangeError(`${label} must be a non-negative number`);
|
|
14
|
+
return Math.floor(number);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createAgentEvent(type, payload = {}, options = {}) {
|
|
18
|
+
if (!AGENT_EVENT_TYPES.includes(type)) throw new RangeError(`unsupported AgentEvent type: ${type}`);
|
|
19
|
+
const event = {
|
|
20
|
+
schema_version: 1,
|
|
21
|
+
type,
|
|
22
|
+
timestamp: options.timestamp || new Date().toISOString(),
|
|
23
|
+
...(options.runId ? { run_id: String(options.runId) } : {}),
|
|
24
|
+
...(Number.isInteger(options.sequence) && options.sequence >= 0 ? { sequence: options.sequence } : {}),
|
|
25
|
+
payload: payload && typeof payload === 'object' ? payload : { value: payload },
|
|
26
|
+
};
|
|
27
|
+
JSON.stringify(event);
|
|
28
|
+
return Object.freeze(event);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createUsageSnapshot(value = {}) {
|
|
32
|
+
const estimateKind = value.estimate_kind === 'provider' ? 'provider' : 'local';
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
current_context: Object.freeze({
|
|
35
|
+
input_tokens: integer(value.current_context?.input_tokens ?? value.currentContextTokens, 'current_context.input_tokens'),
|
|
36
|
+
window_tokens: integer(value.current_context?.window_tokens ?? value.windowTokens, 'current_context.window_tokens'),
|
|
37
|
+
}),
|
|
38
|
+
cumulative: Object.freeze({
|
|
39
|
+
input_tokens: integer(value.cumulative?.input_tokens ?? value.inputTokens, 'cumulative.input_tokens'),
|
|
40
|
+
output_tokens: integer(value.cumulative?.output_tokens ?? value.outputTokens, 'cumulative.output_tokens'),
|
|
41
|
+
cached_input_tokens: integer(value.cumulative?.cached_input_tokens ?? value.cachedInputTokens, 'cumulative.cached_input_tokens'),
|
|
42
|
+
cache_write_tokens: integer(value.cumulative?.cache_write_tokens ?? value.cacheWriteTokens, 'cumulative.cache_write_tokens'),
|
|
43
|
+
reasoning_tokens: integer(value.cumulative?.reasoning_tokens ?? value.reasoningTokens, 'cumulative.reasoning_tokens'),
|
|
44
|
+
}),
|
|
45
|
+
estimate_kind: estimateKind,
|
|
46
|
+
provider_authoritative: estimateKind === 'provider',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
2
|
+
|
|
3
|
+
export function hydrateToolSchemas(catalog = [], selected = [], options = {}) {
|
|
4
|
+
if (!Array.isArray(catalog)) throw new TypeError('catalog must be an array');
|
|
5
|
+
if (!Array.isArray(selected)) throw new TypeError('selected must be an array');
|
|
6
|
+
const maxTools = Number.isInteger(options.maxTools) && options.maxTools > 0 ? options.maxTools : 8;
|
|
7
|
+
const maxChars = Number.isInteger(options.maxChars) && options.maxChars > 0 ? options.maxChars : 40_000;
|
|
8
|
+
const wanted = [...new Set(selected.map(clean).filter(Boolean))].slice(0, maxTools);
|
|
9
|
+
const byName = new Map(catalog.map((tool) => [clean(tool.tool || tool.name), tool]).filter(([name]) => name));
|
|
10
|
+
const tools = [];
|
|
11
|
+
const missing = [];
|
|
12
|
+
const deferred = [];
|
|
13
|
+
let schemaChars = 0;
|
|
14
|
+
|
|
15
|
+
for (const name of wanted) {
|
|
16
|
+
const tool = byName.get(name);
|
|
17
|
+
if (!tool) { missing.push(name); continue; }
|
|
18
|
+
const size = JSON.stringify(tool).length;
|
|
19
|
+
if (schemaChars + size > maxChars) { deferred.push(name); continue; }
|
|
20
|
+
tools.push(Object.freeze({ ...tool }));
|
|
21
|
+
schemaChars += size;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
tools: Object.freeze(tools),
|
|
26
|
+
receipt: Object.freeze({
|
|
27
|
+
catalog_items: catalog.length,
|
|
28
|
+
requested_tools: wanted.length,
|
|
29
|
+
hydrated_tools: tools.length,
|
|
30
|
+
schema_chars: schemaChars,
|
|
31
|
+
missing_tools: Object.freeze(missing),
|
|
32
|
+
deferred_tools: Object.freeze(deferred),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
}
|
package/src/tools/index.js
CHANGED