@skyold/protocol-engine 0.1.0-beta.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/LICENSE +201 -0
- package/README.md +466 -0
- package/README.zh-CN.md +309 -0
- package/dist/contracts.d.ts +84 -0
- package/dist/contracts.js +1 -0
- package/dist/engine.d.ts +4 -0
- package/dist/engine.js +352 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/target-binding.d.ts +12 -0
- package/dist/target-binding.js +29 -0
- package/package.json +65 -0
package/dist/engine.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { providerCapabilityFromTarget, validateProviderDispatchInputV2, validateProviderAsyncJobList, validateProviderAsyncJobSnapshot, validateProviderAsyncJobStart, validateProviderExecutionStart, } from '@skyold/model-protocol';
|
|
2
|
+
import { PROTOCOL_ENGINE_API_VERSION, } from './contracts.js';
|
|
3
|
+
import { PROTOCOL_ENGINE_ERROR_CODES_V1, ProtocolEngineErrorV1, isProtocolEngineErrorV1, } from './errors.js';
|
|
4
|
+
export function createProtocolEngineV1(input) {
|
|
5
|
+
const protocolFamilies = protocolFamilyInventory(input.registry);
|
|
6
|
+
return {
|
|
7
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
8
|
+
protocolFamilies,
|
|
9
|
+
jobs: {
|
|
10
|
+
async get(jobInput) {
|
|
11
|
+
const adapter = await bindAsyncJobAdapter(input.registry, jobInput.binding, jobInput.signal, protocolFamilies, 'get');
|
|
12
|
+
const snapshot = await callAdapter(() => adapter.get(jobInput.providerJobId, jobInput.signal), jobInput.signal);
|
|
13
|
+
return {
|
|
14
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
15
|
+
snapshot: validateAdapterOutput(() => validateProviderAsyncJobSnapshot(snapshot)),
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
async list(jobInput) {
|
|
19
|
+
const adapter = await bindAsyncJobAdapter(input.registry, jobInput.binding, jobInput.signal, protocolFamilies, 'list');
|
|
20
|
+
const adapterResult = await callAdapter(() => adapter.list({
|
|
21
|
+
pageNum: jobInput.pageNum,
|
|
22
|
+
pageSize: jobInput.pageSize,
|
|
23
|
+
...(jobInput.status === undefined ? {} : { status: jobInput.status }),
|
|
24
|
+
...(jobInput.providerJobIds === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { providerJobIds: jobInput.providerJobIds }),
|
|
27
|
+
}, jobInput.signal), jobInput.signal);
|
|
28
|
+
const result = validateAdapterOutput(() => validateProviderAsyncJobList(adapterResult));
|
|
29
|
+
return {
|
|
30
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
31
|
+
items: result.items,
|
|
32
|
+
total: result.total,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
async cancel(jobInput) {
|
|
36
|
+
const adapter = await bindAsyncJobAdapter(input.registry, jobInput.binding, jobInput.signal, protocolFamilies, 'cancel');
|
|
37
|
+
await callAdapter(() => adapter.cancel(jobInput.providerJobId, jobInput.signal), jobInput.signal);
|
|
38
|
+
return { apiVersion: PROTOCOL_ENGINE_API_VERSION };
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
async execute(executionInput) {
|
|
42
|
+
const binding = executionInput.binding;
|
|
43
|
+
const capability = validateEngineInput(() => providerCapabilityFromTarget(binding.target));
|
|
44
|
+
const fixedInput = {
|
|
45
|
+
task: executionInput.task,
|
|
46
|
+
capability,
|
|
47
|
+
target: binding.target,
|
|
48
|
+
signal: executionInput.signal,
|
|
49
|
+
...(executionInput.preparedMedia === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { preparedMedia: executionInput.preparedMedia }),
|
|
52
|
+
};
|
|
53
|
+
validateEngineInput(() => validateProviderDispatchInputV2(fixedInput));
|
|
54
|
+
assertProtocolFamilyDeclared(protocolFamilies, binding.target.capability.protocolFamily);
|
|
55
|
+
if (executionInput.task.executionMode === 'async') {
|
|
56
|
+
return executeAsync(input.registry, executionInput, binding, capability);
|
|
57
|
+
}
|
|
58
|
+
const factory = requireSyncAdapterFactory(input.registry, binding.target.capability.protocolFamily);
|
|
59
|
+
const transport = await bindTransport(binding, executionInput.signal);
|
|
60
|
+
const adapter = bindSyncAdapter(factory, binding, transport);
|
|
61
|
+
if (!adapterSupports(adapter.supports.bind(adapter), capability, executionInput.signal)) {
|
|
62
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.CAPABILITY_UNSUPPORTED, 'PROVIDER_ADAPTER_UNSUPPORTED_CAPABILITY');
|
|
63
|
+
}
|
|
64
|
+
const adapterStart = await callAdapter(() => adapter.start({
|
|
65
|
+
task: executionInput.task,
|
|
66
|
+
capability,
|
|
67
|
+
signal: executionInput.signal,
|
|
68
|
+
...(executionInput.preparedMedia === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { preparedMedia: executionInput.preparedMedia }),
|
|
71
|
+
}), executionInput.signal);
|
|
72
|
+
const started = validateAdapterOutput(() => validateProviderExecutionStart(adapterStart, executionInput.task.taskKind));
|
|
73
|
+
if (started.kind === 'sync') {
|
|
74
|
+
const result = {
|
|
75
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
76
|
+
kind: 'response',
|
|
77
|
+
acceptance: started.acceptance,
|
|
78
|
+
responseMetadata: started.responseMetadata,
|
|
79
|
+
terminal: started.terminal,
|
|
80
|
+
};
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
85
|
+
kind: 'stream',
|
|
86
|
+
acceptance: started.acceptance,
|
|
87
|
+
responseMetadata: started.responseMetadata,
|
|
88
|
+
events: mapStreamErrors(started.events, executionInput.signal),
|
|
89
|
+
terminal: mapStreamTerminalError(started.terminal, executionInput.signal),
|
|
90
|
+
abort: (reason) => abortAdapter((abortReason) => started.abort(abortReason), reason),
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function protocolFamilyInventory(registry) {
|
|
96
|
+
const listed = requireRegistryFactory(() => [...registry.listProtocolFamilies()]);
|
|
97
|
+
if (listed.some((family) => !isProtocolFamily(family))) {
|
|
98
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID, 'PROVIDER_ADAPTER_REGISTRY_INVALID_PROTOCOL_FAMILY');
|
|
99
|
+
}
|
|
100
|
+
if (new Set(listed).size !== listed.length) {
|
|
101
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID, 'PROVIDER_ADAPTER_REGISTRY_DUPLICATE_PROTOCOL_FAMILY');
|
|
102
|
+
}
|
|
103
|
+
return Object.freeze(listed.sort());
|
|
104
|
+
}
|
|
105
|
+
function isProtocolFamily(value) {
|
|
106
|
+
return typeof value === 'string' && /^[^/\s]+\/v\d+$/u.test(value);
|
|
107
|
+
}
|
|
108
|
+
function assertProtocolFamilyDeclared(protocolFamilies, protocolFamily) {
|
|
109
|
+
if (!protocolFamilies.includes(protocolFamily)) {
|
|
110
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.PROTOCOL_FAMILY_UNAVAILABLE, 'PROVIDER_ADAPTER_REGISTRY_PROTOCOL_FAMILY_NOT_DECLARED');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function requireSyncAdapterFactory(registry, protocolFamily) {
|
|
114
|
+
const factory = requireRegistryFactory(() => registry.require(protocolFamily));
|
|
115
|
+
assertFactoryProtocolFamily(factory.protocolFamily, protocolFamily);
|
|
116
|
+
return factory;
|
|
117
|
+
}
|
|
118
|
+
function requireAsyncAdapterFactory(registry, protocolFamily) {
|
|
119
|
+
const factory = requireRegistryFactory(() => registry.requireAsync?.(protocolFamily));
|
|
120
|
+
if (!factory) {
|
|
121
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.PROTOCOL_FAMILY_UNAVAILABLE, 'PROVIDER_ASYNC_ADAPTER_NOT_CONFIGURED');
|
|
122
|
+
}
|
|
123
|
+
assertFactoryProtocolFamily(factory.protocolFamily, protocolFamily);
|
|
124
|
+
assertAsyncJobOperations(factory.jobOperations);
|
|
125
|
+
return factory;
|
|
126
|
+
}
|
|
127
|
+
const ASYNC_JOB_OPERATIONS = new Set(['get', 'list', 'cancel']);
|
|
128
|
+
function assertAsyncJobOperations(operations) {
|
|
129
|
+
if (!Array.isArray(operations) ||
|
|
130
|
+
operations.some((operation) => !ASYNC_JOB_OPERATIONS.has(operation)) ||
|
|
131
|
+
new Set(operations).size !== operations.length) {
|
|
132
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID, 'PROVIDER_ASYNC_ADAPTER_JOB_OPERATIONS_INVALID');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function assertFactoryProtocolFamily(factoryProtocolFamily, requestedProtocolFamily) {
|
|
136
|
+
if (factoryProtocolFamily !== requestedProtocolFamily) {
|
|
137
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID, 'PROVIDER_ADAPTER_REGISTRY_FACTORY_PROTOCOL_FAMILY_MISMATCH');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function bindAsyncJobAdapter(registry, binding, signal, protocolFamilies, operation) {
|
|
141
|
+
const capability = validateEngineInput(() => providerCapabilityFromTarget(binding.target));
|
|
142
|
+
const protocolFamily = capability.protocolFamily;
|
|
143
|
+
assertProtocolFamilyDeclared(protocolFamilies, protocolFamily);
|
|
144
|
+
if (!capability.executionModes.includes('async')) {
|
|
145
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ASYNC_TARGET_REQUIRED, 'PROVIDER_ASYNC_JOB_TARGET_REQUIRED');
|
|
146
|
+
}
|
|
147
|
+
const factory = requireAsyncAdapterFactory(registry, protocolFamily);
|
|
148
|
+
if (!factory.jobOperations.includes(operation)) {
|
|
149
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.CAPABILITY_UNSUPPORTED, 'PROVIDER_ASYNC_JOB_OPERATION_UNSUPPORTED');
|
|
150
|
+
}
|
|
151
|
+
const transport = await bindTransport(binding, signal);
|
|
152
|
+
const adapter = bindAsyncAdapter(factory, binding, transport);
|
|
153
|
+
if (!adapterSupports(adapter.supports.bind(adapter), capability, signal)) {
|
|
154
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.CAPABILITY_UNSUPPORTED, 'PROVIDER_ADAPTER_UNSUPPORTED_CAPABILITY');
|
|
155
|
+
}
|
|
156
|
+
return adapter;
|
|
157
|
+
}
|
|
158
|
+
async function executeAsync(registry, executionInput, binding, capability) {
|
|
159
|
+
const factory = requireAsyncAdapterFactory(registry, binding.target.capability.protocolFamily);
|
|
160
|
+
const transport = await bindTransport(binding, executionInput.signal);
|
|
161
|
+
const adapter = bindAsyncAdapter(factory, binding, transport);
|
|
162
|
+
if (!adapterSupports(adapter.supports.bind(adapter), capability, executionInput.signal)) {
|
|
163
|
+
throw protocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.CAPABILITY_UNSUPPORTED, 'PROVIDER_ADAPTER_UNSUPPORTED_CAPABILITY');
|
|
164
|
+
}
|
|
165
|
+
const adapterStart = await callAdapter(() => adapter.start({
|
|
166
|
+
task: executionInput.task,
|
|
167
|
+
capability,
|
|
168
|
+
signal: executionInput.signal,
|
|
169
|
+
...(executionInput.preparedMedia === undefined
|
|
170
|
+
? {}
|
|
171
|
+
: { preparedMedia: executionInput.preparedMedia }),
|
|
172
|
+
...(executionInput.providerCallbackUrl === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: { providerCallbackUrl: executionInput.providerCallbackUrl }),
|
|
175
|
+
}), executionInput.signal);
|
|
176
|
+
const started = validateAdapterOutput(() => validateProviderAsyncJobStart(adapterStart));
|
|
177
|
+
if (started.kind === 'async') {
|
|
178
|
+
return {
|
|
179
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
180
|
+
kind: 'accepted-job',
|
|
181
|
+
acceptance: started.acceptance,
|
|
182
|
+
responseMetadata: started.responseMetadata,
|
|
183
|
+
providerJobId: started.providerJobId,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
apiVersion: PROTOCOL_ENGINE_API_VERSION,
|
|
188
|
+
kind: 'rejected',
|
|
189
|
+
acceptance: started.acceptance,
|
|
190
|
+
responseMetadata: started.responseMetadata,
|
|
191
|
+
failureCode: started.failureCode,
|
|
192
|
+
retryable: started.retryable,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function requireRegistryFactory(load) {
|
|
196
|
+
try {
|
|
197
|
+
return load();
|
|
198
|
+
}
|
|
199
|
+
catch (cause) {
|
|
200
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID, cause);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function bindSyncAdapter(factory, binding, transport) {
|
|
204
|
+
try {
|
|
205
|
+
return factory.bind({
|
|
206
|
+
providerModel: binding.target.providerModel,
|
|
207
|
+
dispatchProtocolFamily: binding.target.capability.protocolFamily,
|
|
208
|
+
transport,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (cause) {
|
|
212
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED, cause);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function bindAsyncAdapter(factory, binding, transport) {
|
|
216
|
+
try {
|
|
217
|
+
return factory.bind({
|
|
218
|
+
providerModel: binding.target.providerModel,
|
|
219
|
+
dispatchProtocolFamily: binding.target.capability.protocolFamily,
|
|
220
|
+
transport,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (cause) {
|
|
224
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED, cause);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function bindTransport(binding, signal) {
|
|
228
|
+
try {
|
|
229
|
+
return await binding.bindTransport(binding.target.connectionRef, signal);
|
|
230
|
+
}
|
|
231
|
+
catch (cause) {
|
|
232
|
+
throw wrapOperationError(PROTOCOL_ENGINE_ERROR_CODES_V1.TRANSPORT_BIND_FAILED, cause, signal);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function adapterSupports(supports, capability, signal) {
|
|
236
|
+
try {
|
|
237
|
+
return supports(capability);
|
|
238
|
+
}
|
|
239
|
+
catch (cause) {
|
|
240
|
+
throw wrapOperationError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED, cause, signal);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function callAdapter(operation, signal) {
|
|
244
|
+
try {
|
|
245
|
+
return await operation();
|
|
246
|
+
}
|
|
247
|
+
catch (cause) {
|
|
248
|
+
throw wrapOperationError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED, cause, signal);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function validateEngineInput(validation) {
|
|
252
|
+
try {
|
|
253
|
+
return validation();
|
|
254
|
+
}
|
|
255
|
+
catch (cause) {
|
|
256
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.INPUT_INVALID, cause, inputCompatibilityMessage(cause));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function validateAdapterOutput(validation) {
|
|
260
|
+
try {
|
|
261
|
+
return validation();
|
|
262
|
+
}
|
|
263
|
+
catch (cause) {
|
|
264
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID, cause, adapterOutputCompatibilityMessage(cause));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function* mapStreamErrors(events, signal) {
|
|
268
|
+
try {
|
|
269
|
+
yield* events;
|
|
270
|
+
}
|
|
271
|
+
catch (cause) {
|
|
272
|
+
const compatibilityMessage = adapterOutputCompatibilityMessage(cause);
|
|
273
|
+
if (compatibilityMessage !== undefined) {
|
|
274
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID, cause, compatibilityMessage);
|
|
275
|
+
}
|
|
276
|
+
throw wrapOperationError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_STREAM_FAILED, cause, signal);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function mapStreamTerminalError(terminal, signal) {
|
|
280
|
+
try {
|
|
281
|
+
return await terminal;
|
|
282
|
+
}
|
|
283
|
+
catch (cause) {
|
|
284
|
+
const compatibilityMessage = adapterOutputCompatibilityMessage(cause);
|
|
285
|
+
if (compatibilityMessage !== undefined) {
|
|
286
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID, cause, compatibilityMessage);
|
|
287
|
+
}
|
|
288
|
+
throw wrapOperationError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_STREAM_FAILED, cause, signal);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function abortAdapter(abort, reason) {
|
|
292
|
+
try {
|
|
293
|
+
abort(reason);
|
|
294
|
+
}
|
|
295
|
+
catch (cause) {
|
|
296
|
+
throw wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED, cause);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function wrapOperationError(code, cause, signal) {
|
|
300
|
+
if (isProtocolEngineErrorV1(cause))
|
|
301
|
+
return cause;
|
|
302
|
+
if (signal.aborted || (cause instanceof Error && cause.name === 'AbortError')) {
|
|
303
|
+
return wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ABORTED, cause);
|
|
304
|
+
}
|
|
305
|
+
return wrapProtocolEngineError(code, cause);
|
|
306
|
+
}
|
|
307
|
+
function wrapProtocolEngineError(code, cause, compatibilityMessage) {
|
|
308
|
+
if (isProtocolEngineErrorV1(cause))
|
|
309
|
+
return cause;
|
|
310
|
+
return new ProtocolEngineErrorV1(code, {
|
|
311
|
+
cause,
|
|
312
|
+
...(compatibilityMessage === undefined ? {} : { compatibilityMessage }),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function protocolEngineError(code, compatibilityMessage) {
|
|
316
|
+
return new ProtocolEngineErrorV1(code, {
|
|
317
|
+
...(compatibilityMessage === undefined ? {} : { compatibilityMessage }),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const INPUT_COMPATIBILITY_MESSAGES = new Set([
|
|
321
|
+
'CONNECTION_REF_REQUIRED',
|
|
322
|
+
'PROTOCOL_FAMILY_MISMATCH',
|
|
323
|
+
'CAPABILITY_PROTOCOL_MISMATCH',
|
|
324
|
+
'TARGET_CAPABILITY_MISMATCH',
|
|
325
|
+
'TASK_KIND_MISMATCH',
|
|
326
|
+
'EXECUTION_MODE_MISMATCH',
|
|
327
|
+
'INPUT_MODALITY_MISMATCH',
|
|
328
|
+
'OUTPUT_MODALITY_MISMATCH',
|
|
329
|
+
]);
|
|
330
|
+
const ADAPTER_OUTPUT_COMPATIBILITY_MESSAGES = new Set([
|
|
331
|
+
'ADAPTER_EXECUTION_START_INVALID',
|
|
332
|
+
'ADAPTER_STREAM_EVENT_INVALID',
|
|
333
|
+
'ADAPTER_TERMINAL_INVALID',
|
|
334
|
+
'ADAPTER_SUCCEEDED_WITHOUT_ACCEPTANCE',
|
|
335
|
+
'ADAPTER_TERMINAL_TASK_KIND_MISMATCH',
|
|
336
|
+
'ADAPTER_TERMINAL_USAGE_MISMATCH',
|
|
337
|
+
'ADAPTER_TERMINAL_RESPONSE_STATUS_MISMATCH',
|
|
338
|
+
'ADAPTER_ACCEPTANCE_MISMATCH',
|
|
339
|
+
'ADAPTER_ASYNC_START_INVALID',
|
|
340
|
+
'ADAPTER_ASYNC_JOB_SNAPSHOT_INVALID',
|
|
341
|
+
'ADAPTER_ASYNC_JOB_LIST_INVALID',
|
|
342
|
+
]);
|
|
343
|
+
function inputCompatibilityMessage(cause) {
|
|
344
|
+
return cause instanceof Error && INPUT_COMPATIBILITY_MESSAGES.has(cause.message)
|
|
345
|
+
? cause.message
|
|
346
|
+
: undefined;
|
|
347
|
+
}
|
|
348
|
+
function adapterOutputCompatibilityMessage(cause) {
|
|
349
|
+
return cause instanceof Error && ADAPTER_OUTPUT_COMPATIBILITY_MESSAGES.has(cause.message)
|
|
350
|
+
? cause.message
|
|
351
|
+
: undefined;
|
|
352
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ProtocolEngineApiVersion } from './contracts.js';
|
|
2
|
+
export declare const PROTOCOL_ENGINE_ERROR_CODES_V1: Readonly<{
|
|
3
|
+
readonly REGISTRY_INVALID: "PROTOCOL_ENGINE_REGISTRY_INVALID";
|
|
4
|
+
readonly PROTOCOL_FAMILY_UNAVAILABLE: "PROTOCOL_ENGINE_PROTOCOL_FAMILY_UNAVAILABLE";
|
|
5
|
+
readonly INPUT_INVALID: "PROTOCOL_ENGINE_INPUT_INVALID";
|
|
6
|
+
readonly CAPABILITY_UNSUPPORTED: "PROTOCOL_ENGINE_CAPABILITY_UNSUPPORTED";
|
|
7
|
+
readonly ASYNC_TARGET_REQUIRED: "PROTOCOL_ENGINE_ASYNC_TARGET_REQUIRED";
|
|
8
|
+
readonly CONNECTION_REF_MISMATCH: "PROTOCOL_ENGINE_CONNECTION_REF_MISMATCH";
|
|
9
|
+
readonly TRANSPORT_BIND_FAILED: "PROTOCOL_ENGINE_TRANSPORT_BIND_FAILED";
|
|
10
|
+
readonly ADAPTER_EXECUTION_FAILED: "PROTOCOL_ENGINE_ADAPTER_EXECUTION_FAILED";
|
|
11
|
+
readonly ADAPTER_OUTPUT_INVALID: "PROTOCOL_ENGINE_ADAPTER_OUTPUT_INVALID";
|
|
12
|
+
readonly ADAPTER_STREAM_FAILED: "PROTOCOL_ENGINE_ADAPTER_STREAM_FAILED";
|
|
13
|
+
readonly ABORTED: "PROTOCOL_ENGINE_ABORTED";
|
|
14
|
+
}>;
|
|
15
|
+
export type ProtocolEngineErrorCodeV1 = (typeof PROTOCOL_ENGINE_ERROR_CODES_V1)[keyof typeof PROTOCOL_ENGINE_ERROR_CODES_V1];
|
|
16
|
+
export type ProtocolEngineErrorPhaseV1 = 'configuration' | 'input' | 'transport' | 'adapter' | 'lifecycle';
|
|
17
|
+
export interface ProtocolEngineErrorOptionsV1 {
|
|
18
|
+
/** Internal diagnostic cause. Hosts must never serialize it to clients. */
|
|
19
|
+
readonly cause?: unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Temporary compatibility message for Hosts that still classify historical
|
|
22
|
+
* fixed-runtime failures by Error.message. New integrations must use code.
|
|
23
|
+
*/
|
|
24
|
+
readonly compatibilityMessage?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stable V1 failure surface for Engine operations.
|
|
28
|
+
*
|
|
29
|
+
* `code`, `phase`, and `apiVersion` are safe classification facts. `cause` is
|
|
30
|
+
* diagnostic-only and can contain Transport or Adapter details.
|
|
31
|
+
*/
|
|
32
|
+
export declare class ProtocolEngineErrorV1 extends Error {
|
|
33
|
+
readonly apiVersion: ProtocolEngineApiVersion;
|
|
34
|
+
readonly code: ProtocolEngineErrorCodeV1;
|
|
35
|
+
readonly phase: ProtocolEngineErrorPhaseV1;
|
|
36
|
+
constructor(code: ProtocolEngineErrorCodeV1, options?: ProtocolEngineErrorOptionsV1);
|
|
37
|
+
}
|
|
38
|
+
export declare function isProtocolEngineErrorV1(error: unknown): error is ProtocolEngineErrorV1;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PROTOCOL_ENGINE_API_VERSION } from './contracts.js';
|
|
2
|
+
export const PROTOCOL_ENGINE_ERROR_CODES_V1 = Object.freeze({
|
|
3
|
+
REGISTRY_INVALID: 'PROTOCOL_ENGINE_REGISTRY_INVALID',
|
|
4
|
+
PROTOCOL_FAMILY_UNAVAILABLE: 'PROTOCOL_ENGINE_PROTOCOL_FAMILY_UNAVAILABLE',
|
|
5
|
+
INPUT_INVALID: 'PROTOCOL_ENGINE_INPUT_INVALID',
|
|
6
|
+
CAPABILITY_UNSUPPORTED: 'PROTOCOL_ENGINE_CAPABILITY_UNSUPPORTED',
|
|
7
|
+
ASYNC_TARGET_REQUIRED: 'PROTOCOL_ENGINE_ASYNC_TARGET_REQUIRED',
|
|
8
|
+
CONNECTION_REF_MISMATCH: 'PROTOCOL_ENGINE_CONNECTION_REF_MISMATCH',
|
|
9
|
+
TRANSPORT_BIND_FAILED: 'PROTOCOL_ENGINE_TRANSPORT_BIND_FAILED',
|
|
10
|
+
ADAPTER_EXECUTION_FAILED: 'PROTOCOL_ENGINE_ADAPTER_EXECUTION_FAILED',
|
|
11
|
+
ADAPTER_OUTPUT_INVALID: 'PROTOCOL_ENGINE_ADAPTER_OUTPUT_INVALID',
|
|
12
|
+
ADAPTER_STREAM_FAILED: 'PROTOCOL_ENGINE_ADAPTER_STREAM_FAILED',
|
|
13
|
+
ABORTED: 'PROTOCOL_ENGINE_ABORTED',
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Stable V1 failure surface for Engine operations.
|
|
17
|
+
*
|
|
18
|
+
* `code`, `phase`, and `apiVersion` are safe classification facts. `cause` is
|
|
19
|
+
* diagnostic-only and can contain Transport or Adapter details.
|
|
20
|
+
*/
|
|
21
|
+
export class ProtocolEngineErrorV1 extends Error {
|
|
22
|
+
apiVersion = PROTOCOL_ENGINE_API_VERSION;
|
|
23
|
+
code;
|
|
24
|
+
phase;
|
|
25
|
+
constructor(code, options = {}) {
|
|
26
|
+
super(options.compatibilityMessage ?? code, { cause: options.cause });
|
|
27
|
+
this.name = 'ProtocolEngineErrorV1';
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.phase = phaseForCode(code);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function isProtocolEngineErrorV1(error) {
|
|
33
|
+
const candidate = error;
|
|
34
|
+
return (error instanceof ProtocolEngineErrorV1 ||
|
|
35
|
+
(candidate instanceof Error &&
|
|
36
|
+
candidate.name === 'ProtocolEngineErrorV1' &&
|
|
37
|
+
candidate.apiVersion === PROTOCOL_ENGINE_API_VERSION &&
|
|
38
|
+
typeof candidate.code === 'string' &&
|
|
39
|
+
isProtocolEngineErrorCodeV1(candidate.code) &&
|
|
40
|
+
typeof candidate.phase === 'string' &&
|
|
41
|
+
isProtocolEngineErrorPhaseV1(candidate.phase)));
|
|
42
|
+
}
|
|
43
|
+
function phaseForCode(code) {
|
|
44
|
+
switch (code) {
|
|
45
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.REGISTRY_INVALID:
|
|
46
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.PROTOCOL_FAMILY_UNAVAILABLE:
|
|
47
|
+
return 'configuration';
|
|
48
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.INPUT_INVALID:
|
|
49
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.CAPABILITY_UNSUPPORTED:
|
|
50
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.ASYNC_TARGET_REQUIRED:
|
|
51
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.CONNECTION_REF_MISMATCH:
|
|
52
|
+
return 'input';
|
|
53
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.TRANSPORT_BIND_FAILED:
|
|
54
|
+
return 'transport';
|
|
55
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_EXECUTION_FAILED:
|
|
56
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID:
|
|
57
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_STREAM_FAILED:
|
|
58
|
+
return 'adapter';
|
|
59
|
+
case PROTOCOL_ENGINE_ERROR_CODES_V1.ABORTED:
|
|
60
|
+
return 'lifecycle';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function isProtocolEngineErrorCodeV1(value) {
|
|
64
|
+
return Object.values(PROTOCOL_ENGINE_ERROR_CODES_V1).some((code) => code === value);
|
|
65
|
+
}
|
|
66
|
+
function isProtocolEngineErrorPhaseV1(value) {
|
|
67
|
+
return ['configuration', 'input', 'transport', 'adapter', 'lifecycle'].includes(value);
|
|
68
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createProtocolEngineV1 } from './engine.js';
|
|
2
|
+
export { PROTOCOL_ENGINE_ERROR_CODES_V1, ProtocolEngineErrorV1, isProtocolEngineErrorV1, type ProtocolEngineErrorCodeV1, type ProtocolEngineErrorOptionsV1, type ProtocolEngineErrorPhaseV1, } from './errors.js';
|
|
3
|
+
export { createProtocolEngineTargetBindingV1, type CreateProtocolEngineTargetBindingInputV1, } from './target-binding.js';
|
|
4
|
+
export { PROTOCOL_ENGINE_API_VERSION, type ProtocolEngineAcceptedJobV1, type ProtocolEngineAdapterRegistryPortV1, type ProtocolEngineApiVersion, type ProtocolEngineAsyncJobCancellationV1, type ProtocolEngineAsyncJobInputV1, type ProtocolEngineAsyncJobListRequestV1, type ProtocolEngineAsyncJobListResultV1, type ProtocolEngineAsyncJobObservationV1, type ProtocolEngineAsyncJobsV1, type ProtocolEngineDispatchTargetV1, type ProtocolEngineExecuteInputV1, type ProtocolEngineExecutionV1, type ProtocolEngineRejectedV1, type ProtocolEngineResponseV1, type ProtocolEngineStreamV1, type ProtocolEngineTargetBindingV1, type ProtocolEngineV1, } from './contracts.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createProtocolEngineV1 } from './engine.js';
|
|
2
|
+
export { PROTOCOL_ENGINE_ERROR_CODES_V1, ProtocolEngineErrorV1, isProtocolEngineErrorV1, } from './errors.js';
|
|
3
|
+
export { createProtocolEngineTargetBindingV1, } from './target-binding.js';
|
|
4
|
+
export { PROTOCOL_ENGINE_API_VERSION, } from './contracts.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProviderTransport } from '@skyold/model-protocol';
|
|
2
|
+
import type { ProtocolEngineDispatchTargetV1, ProtocolEngineTargetBindingV1 } from './contracts.js';
|
|
3
|
+
export interface CreateProtocolEngineTargetBindingInputV1 {
|
|
4
|
+
readonly target: ProtocolEngineDispatchTargetV1;
|
|
5
|
+
readonly bindTransport: (signal: AbortSignal) => Promise<ProviderTransport> | ProviderTransport;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds the reusable Host-to-Engine binding and makes the target connection
|
|
9
|
+
* reference authoritative. Host policy supplies credentials and Transport;
|
|
10
|
+
* Engine callers cannot substitute a different connection reference.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createProtocolEngineTargetBindingV1(input: CreateProtocolEngineTargetBindingInputV1): ProtocolEngineTargetBindingV1;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PROTOCOL_ENGINE_ERROR_CODES_V1, ProtocolEngineErrorV1 } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the reusable Host-to-Engine binding and makes the target connection
|
|
4
|
+
* reference authoritative. Host policy supplies credentials and Transport;
|
|
5
|
+
* Engine callers cannot substitute a different connection reference.
|
|
6
|
+
*/
|
|
7
|
+
export function createProtocolEngineTargetBindingV1(input) {
|
|
8
|
+
const target = immutableSnapshot(input.target);
|
|
9
|
+
const authoritativeConnectionRef = target.connectionRef;
|
|
10
|
+
const bindTransport = input.bindTransport;
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
target,
|
|
13
|
+
async bindTransport(connectionRef, signal) {
|
|
14
|
+
if (connectionRef !== authoritativeConnectionRef) {
|
|
15
|
+
throw new ProtocolEngineErrorV1(PROTOCOL_ENGINE_ERROR_CODES_V1.CONNECTION_REF_MISMATCH);
|
|
16
|
+
}
|
|
17
|
+
return bindTransport(signal);
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function immutableSnapshot(value) {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return Object.freeze(value.map((item) => immutableSnapshot(item)));
|
|
24
|
+
}
|
|
25
|
+
if (value !== null && typeof value === 'object') {
|
|
26
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, immutableSnapshot(item)])));
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skyold/protocol-engine",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Host-neutral execution kernel for normalized AI provider protocols.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.0.0"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/contracts.d.ts",
|
|
21
|
+
"dist/contracts.js",
|
|
22
|
+
"dist/engine.d.ts",
|
|
23
|
+
"dist/engine.js",
|
|
24
|
+
"dist/errors.d.ts",
|
|
25
|
+
"dist/errors.js",
|
|
26
|
+
"dist/index.d.ts",
|
|
27
|
+
"dist/index.js",
|
|
28
|
+
"dist/target-binding.d.ts",
|
|
29
|
+
"dist/target-binding.js",
|
|
30
|
+
"README.zh-CN.md"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/tokenforgegateway/tokenforge-protocol-engine.git",
|
|
35
|
+
"directory": "packages/protocol-engine"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/tokenforgegateway/tokenforge-protocol-engine/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/tokenforgegateway/tokenforge-protocol-engine#readme",
|
|
41
|
+
"keywords": [
|
|
42
|
+
"ai",
|
|
43
|
+
"llm",
|
|
44
|
+
"protocol",
|
|
45
|
+
"adapter",
|
|
46
|
+
"gateway",
|
|
47
|
+
"tokenforge"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@skyold/model-protocol": "^0.1.0-beta.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "22.19.19",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"vitest": "2.1.9"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc -b",
|
|
62
|
+
"check": "pnpm build && pnpm test",
|
|
63
|
+
"test": "vitest run"
|
|
64
|
+
}
|
|
65
|
+
}
|