@skyold/protocol-engine 0.1.0-beta.0 → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,15 +25,16 @@ its own TypeScript build, its own test command, and one runtime dependency:
25
25
  concrete Provider Adapters, storage, HTTP frameworks, credential stores, or
26
26
  network clients.
27
27
 
28
- The package and its normalized-contract dependency are configured for public
29
- npm access, but no registry release has been made yet. Therefore:
28
+ The package and its normalized-contract dependency have a public
29
+ `0.1.0-beta.1` release under the `beta` dist-tag. Therefore:
30
30
 
31
31
  - it is independently embeddable and testable by packages in this repository;
32
32
  - its packed runtime surface is independently inspectable;
33
33
  - packed tarballs can be installed and exercised by an isolated external
34
34
  consumer;
35
- - public registry availability, a versioned release, and provenance signing are
36
- not claimed until the release gate completes.
35
+ - public registry installation has been smoke-tested from an isolated consumer;
36
+ - exact-SHA CI for the release-preparation tree, downstream adoption,
37
+ provenance signing, and real-Provider acceptance remain separate gates.
37
38
 
38
39
  The local V1 core implementation is complete against its frozen execution
39
40
  boundary. Overall rollout certification is still incomplete: fixture-backed
@@ -302,6 +303,17 @@ selects the protocol Adapter, and returns one versioned execution start:
302
303
  - `accepted-job` for durable asynchronous Provider acceptance;
303
304
  - `rejected` for not-accepted or acceptance-unknown asynchronous starts.
304
305
 
306
+ When a Provider response was received, `providerResponse` carries its status,
307
+ headers, and body bytes as the authoritative result. Successfully parsed and
308
+ semantically validated non-failure stream events carry their exact SSE frame in
309
+ `providerWire`; normalized terminal and event fields are derived observations of
310
+ that same result. Provider errors, malformed or unknown frames, cancellation,
311
+ and bytes after a terminal never use raw passthrough and produce only bounded
312
+ safe egress. Caller-facing direct egress projects headers through
313
+ `projectProviderResponseHeadersForCallerV1`: connection, credential, cookie,
314
+ representation-length/encoding, caching, CORS, rate-limit, and security-policy
315
+ fields remain Host-owned and are not copied from upstream.
316
+
305
317
  Use `engine.jobs.get`, `engine.jobs.list`, and `engine.jobs.cancel` for later
306
318
  async-job operations. Persistence, polling, callbacks, materialization, usage,
307
319
  and settlement remain Host responsibilities.
@@ -315,25 +327,36 @@ support `get` without pretending to support `list` or `cancel`.
315
327
  ### Provider response compatibility
316
328
 
317
329
  Provider wire responses are an open-world boundary. An Adapter validates and
318
- extracts the fields understood by its protocol version, ignores additive fields
319
- in synchronous bodies and known stream frames, and never retains those unknown
320
- fields in normalized output. This lets a Provider add response metadata without
321
- breaking an otherwise compatible protocol implementation.
330
+ extracts the fields understood by its protocol version as derived normalized
331
+ facts. For same-protocol egress, status, raw headers, and body bytes remain the
332
+ authoritative response. A recognized non-failure SSE event retains its exact
333
+ validated frame, including additive fields. Unknown event kinds, malformed
334
+ frames, Provider failures, cancellation, opaque passthrough, and post-terminal
335
+ bytes fail closed or use bounded normalized egress; they are never returned as
336
+ raw caller-visible wire. Headers cross a separate caller-safe projection so
337
+ transport or security metadata for one HTTP hop is not mistaken for another.
322
338
 
323
339
  Compatibility does not turn malformed known facts into success. Missing,
324
340
  mistyped, conflicting, duplicated, unsafe, or semantically invalid known fields
325
- still fail. An unknown stream event kind can alter lifecycle semantics, so it is
326
- not treated as a harmless additive field and fails unless the protocol Adapter
327
- explicitly supports it. Parser depth, body-size, URL, base64, index, usage, and
328
- terminal-consistency limits remain enforced.
341
+ make the derived normalization fail. Parser depth, body-size, URL, base64, index,
342
+ usage, and terminal-consistency limits continue to protect normalization and
343
+ execution; transport or framing failures can of course prevent a complete
344
+ response from being received.
329
345
 
330
346
  The Engine then validates Adapter outputs before exposing them to a Host. This
331
347
  includes sync/stream acceptance, terminal structure and consistency, every
332
- normalized stream event, plus async start, snapshot, and list result structure.
348
+ normalized stream event, plus async start, snapshot, list, and cancellation
349
+ result structure.
333
350
  Normalized event, metadata, terminal, result, usage, Responses, and async-job
334
- objects are closed schemas: undeclared fields are rejected. Raw Provider
335
- extensions therefore cannot leak through an Adapter to Host egress, usage, or
336
- settlement.
351
+ objects are closed schemas: undeclared derived facts are rejected. The explicit
352
+ Provider response envelope is not part of that closed projection. Hosts may use
353
+ only normalized facts for usage or settlement while returning the authoritative
354
+ Provider response to a same-protocol caller.
355
+
356
+ When an async follow-up receives a complete non-success Provider response, the
357
+ Engine error exposes that validated response in `providerResponse`. Hosts must
358
+ never serialize the whole Engine error or its diagnostic `cause`; they may
359
+ explicitly apply same-protocol egress policy to the response envelope.
337
360
 
338
361
  Terminal results must match the requested Task kind, and duplicate result,
339
362
  terminal, and Responses usage facts must agree when present. A custom Adapter
@@ -371,13 +394,13 @@ No test suite can mathematically guarantee all future Engine functionality.
371
394
  TokenForge uses separate proof layers so a narrow unit test is not mistaken for
372
395
  complete proxy evidence.
373
396
 
374
- | Layer | Command | What it proves | What it does not prove |
375
- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
376
- | Engine package | `pnpm --dir packages/protocol-engine check` | Independent build plus core Registry, binding, sync, stream, async-job, validation and Host-equivalence tests. | Concrete Adapter wire formats, Host persistence, live network. |
377
- | Built-in conformance | `pnpm --filter @skyold/provider-adapters test -- src/conformance/runtime-conformance.test.ts` | Every registered built-in family executes through public Engine API with fixture Transports. | Provider credentials or current remote behavior. |
378
- | Architecture boundary | `pnpm --filter @tokenforge/acceptance test -- src/L1-kernel/import-boundary.test.ts` | Engine dependency isolation, package surface, no raw-forward/direct Adapter bypass, Host neutrality. | Runtime semantic correctness by itself. |
379
- | Host integration | Targeted suites for each embedding application and repository `pnpm check` | Caller-specific context binding, streams, media persistence, usage and settlement compatibility. | Exact deployed SHA or live Provider availability. |
380
- | Real Provider certification | `pnpm certify:protocol-engine-real --manifest /protected/path/suite.json --run --env-file .env --ack-real-provider-calls I_AUTHORIZE_REAL_PROVIDER_CALLS` | Current authenticated Provider behavior for every successful evidence shard. | Deployment, billing correctness, or a missing family. |
397
+ | Layer | Command | What it proves | What it does not prove |
398
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
399
+ | Engine package | `pnpm --dir packages/protocol-engine check` | Independent build plus core Registry, binding, sync, stream, async-job, validation and Host-equivalence tests. | Concrete Adapter wire formats, Host persistence, live network. |
400
+ | Built-in conformance | `pnpm --filter @skyold/provider-adapters test -- src/conformance/runtime-conformance.test.ts` | Every registered built-in family executes through public Engine API with fixture Transports. | Provider credentials or current remote behavior. |
401
+ | Architecture boundary | `pnpm --filter @tokenforge/acceptance test -- src/L1-kernel/import-boundary.test.ts` (TokenForge source monorepo) | Engine dependency isolation, package surface, explicit Provider response ownership through Adapters, Host neutrality. | Runtime semantic correctness by itself. |
402
+ | Host integration | Targeted suites for each embedding application and repository `pnpm check` | Caller-specific context binding, streams, media persistence, usage and settlement compatibility. | Exact deployed SHA or live Provider availability. |
403
+ | Real Provider certification | `pnpm certify:protocol-engine-real --manifest /protected/path/suite.json --run --env-file .env --ack-real-provider-calls I_AUTHORIZE_REAL_PROVIDER_CALLS` (TokenForge source monorepo) | Current authenticated Provider behavior for every successful evidence shard. | Deployment, billing correctness, or a missing family. |
381
404
 
382
405
  For a local Engine change, the minimum independent gate is the Engine package
383
406
  command. A protocol or Adapter change must also pass built-in conformance. A
@@ -388,9 +411,11 @@ gates.
388
411
 
389
412
  ## Real-Provider certification
390
413
 
391
- The repository's opt-in certification Host drives this public Engine API and
392
- the built-in Adapter Registry directly; it does not enter through any product
393
- application route. A versioned JSON manifest supplies one or more cases for every
414
+ The TokenForge source monorepo ships the opt-in certification Host that drives
415
+ this public Engine API and the built-in Adapter Registry directly; it does not
416
+ enter through any product application route. The certification commands in this
417
+ section run in that monorepo, not in this repository. A versioned JSON manifest
418
+ supplies one or more cases for every
394
419
  protocol family returned by `engine.protocolFamilies`, with resolved target and
395
420
  capability facts, normalized tasks, secret environment-variable names, exact
396
421
  Provider domains, and suite request/time ceilings. Pricing and Provider billing
@@ -434,6 +459,7 @@ Host can perform a one-request, read-only diagnostic through `engine.jobs.list`
434
459
  without creating another task:
435
460
 
436
461
  ```bash
462
+ # Run in the TokenForge source monorepo (apps/acceptance), not in this repository.
437
463
  pnpm --filter @tokenforge/acceptance diagnose:protocol-engine-real-async \
438
464
  --manifest /protected/path/async-shard.json \
439
465
  --env-file /protected/path/provider.env \
@@ -451,6 +477,7 @@ exhausted after the Provider had accepted the task, recover that exact submitted
451
477
  task without creating another one:
452
478
 
453
479
  ```bash
480
+ # Run in the TokenForge source monorepo (apps/acceptance), not in this repository.
454
481
  pnpm --filter @tokenforge/acceptance recover:protocol-engine-real-shard \
455
482
  --manifest /protected/path/async-shard.json \
456
483
  --failure-evidence /protected/path/polling-exhausted.json \
package/README.zh-CN.md CHANGED
@@ -12,12 +12,13 @@
12
12
 
13
13
  引擎是一个独立的 workspace package,提供唯一的包根 ESM API、独立的 TypeScript 构建和测试命令,且只有一个运行时依赖:`@skyold/model-protocol`。它不依赖任何 TokenForge Host、具体 Provider Adapter、存储、HTTP 框架、凭证仓库或网络客户端。
14
14
 
15
- 该包及其规范化协议依赖已经配置公开 npm 元数据,但尚未执行 registry 首次发布。因此,当前可以确认的是:
15
+ 该包及其规范化协议依赖已通过 `beta` dist-tag 公开发布为 `0.1.0-beta.1`。因此,当前可以确认的是:
16
16
 
17
17
  - 仓库内其他 package 可以独立嵌入并测试它;
18
18
  - 可以独立检查它的打包运行时边界;
19
19
  - 打包 tarball 已可由隔离的外部消费者安装与验证;
20
- - 在发布门禁完成前,不承诺公共 registry 可用性、版本化发布或来源签名。
20
+ - 已从隔离消费者通过公共 registry 安装并完成运行 smoke;
21
+ - 发布准备树的 exact-SHA CI、下游采用、来源签名和真实 Provider 验收仍是独立门禁。
21
22
 
22
23
  本地 V1 核心已经完成并冻结在既定执行边界内。整体上线认证尚未完成:九种内置协议族的 fixture conformance 已全部通过,当前已有七种协议的真实 Provider 成功证据;`openai-embeddings/v1` 和 `openai-images/v1` 仍待真实成功认证。公开发布、exact-SHA CI 和部署属于独立交付门禁,不是把 Host 业务逻辑加入引擎的理由。
23
24
 
@@ -225,17 +226,21 @@ try {
225
226
  - `accepted-job`:Provider 已持久接受异步任务;
226
227
  - `rejected`:异步启动未被接受,或接受状态未知。
227
228
 
229
+ 只要已经收到 Provider 响应,`providerResponse` 就以权威结果的身份携带状态码、响应头和 body 原始字节。成功解析并通过语义校验的非失败流事件会通过 `providerWire` 携带其精确 SSE frame;规范化 terminal 和 event 是对同一结果的派生观察。Provider error、malformed/unknown frame、取消、opaque passthrough 和终态后的字节一律不得 raw passthrough,只能产生有界的安全下行。面向调用方的 direct egress 还会通过 `projectProviderResponseHeadersForCallerV1` 投影响应头:连接级、凭证、Cookie、表示长度/编码、缓存、CORS、限流和安全策略字段都由 Host 持有,不能从上游直接复制。
230
+
228
231
  后续异步任务操作使用 `engine.jobs.get`、`engine.jobs.list` 和 `engine.jobs.cancel`。持久化、轮询、回调、物化、用量和结算仍是 Host 职责。
229
232
 
230
233
  异步 Adapter factory 通过 `jobOperations` 声明支持的后续操作。引擎会在绑定 Transport 前,以 `PROTOCOL_ENGINE_CAPABILITY_UNSUPPORTED` 拒绝未声明的操作。因此协议可以保留真实生命周期差异:例如,一个协议可以支持 `get`,而不需要伪装成支持 `list` 或 `cancel`。
231
234
 
232
235
  ### Provider response 兼容原则
233
236
 
234
- Provider wire response 是开放世界边界。Adapter 校验并提取当前协议版本理解的字段,忽略同步 body 和已知 stream frame 中新增的附加字段,且不会把这些未知字段保留到规范化输出中。这样 Provider 新增响应元数据时,不会破坏原本兼容的协议实现。
237
+ Provider wire response 是开放世界边界。Adapter 会把当前协议版本理解的字段提取为派生的规范化事实;对于同协议下行,状态码、原始响应头和 body 原始字节仍是权威结果。已识别的非失败 SSE 事件会保留其通过校验的精确 frame,包括附加字段。未知事件类型、malformed frame、Provider failure、取消、opaque passthrough 和终态后字节必须 fail closed 或使用有界的规范化下行,绝不能作为 raw wire 返回调用方。响应头另行经过 caller-safe 投影,避免把上一段 HTTP 连接或安全元数据误用于下一段连接。
238
+
239
+ 兼容不代表把已知字段异常当作成功。已知字段缺失、类型错误、冲突、重复、不安全或语义无效会导致派生归一化失败。解析深度、body 大小、URL、base64、index、usage 和终态一致性限制继续保护归一化与执行;Transport 或 framing 失败则可能使完整响应根本无法被接收。
235
240
 
236
- 兼容不代表把已知字段异常当作成功。已知字段缺失、类型错误、冲突、重复、不安全或语义无效仍然失败。未知 stream event kind 可能改变生命周期语义,不能视为无害附加字段;除非协议 Adapter 明确支持,否则必须失败。解析深度、body 大小、URLbase64index、usage 和终态一致性限制继续生效。
241
+ 随后,引擎会在向 Host 暴露结果前校验 Adapter 输出,包括同步/流式接受结果、终态结构与一致性、每个规范化流事件,以及异步启动、snapshot、列表和取消结果结构。规范化 eventmetadataterminalresult、usage、Responses 和异步任务对象仍是封闭 schema:未声明的派生事实会被拒绝。显式 Provider response envelope 不属于这个封闭投影;Host 可以只用规范化事实处理用量或结算,同时把权威 Provider 响应返回给同协议调用者。
237
242
 
238
- 随后,引擎会在向 Host 暴露结果前校验 Adapter 输出,包括同步/流式接受结果、终态结构与一致性、每个规范化流事件,以及异步启动、snapshot 和列表结果结构。规范化 event、metadata、terminal、result、usage、Responses 和异步任务对象都是封闭 schema:未声明字段会被拒绝。因此 Provider 原始扩展字段不能穿过 Adapter 泄漏到 Host egress、用量或结算流程。
243
+ 异步后续操作收到完整的非成功 Provider 响应时,引擎错误会在 `providerResponse` 中暴露经过校验的响应。Host 绝不能序列化整个引擎错误或诊断用 `cause`;只能显式地对该响应 envelope 应用同协议 egress 策略。
239
244
 
240
245
  终态 result 必须与请求的 Task kind 一致;当 result、terminal 和 Responses 中重复出现 usage 事实时,它们必须一致。自定义 Adapter 不能依赖 TypeScript 类型断言绕过 V1 运行时契约,也不能让 Host egress 和结算观察到不同事实。
241
246
 
@@ -251,19 +256,19 @@ Provider wire response 是开放世界边界。Adapter 校验并提取当前协
251
256
 
252
257
  任何测试套件都无法在数学意义上保证未来全部引擎功能。TokenForge 把证明拆分为不同层级,避免把一个狭窄单元测试误认为完整 proxy 证据。
253
258
 
254
- | 层级 | 命令 | 能证明 | 不能证明 |
255
- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------- |
256
- | 引擎 package | `pnpm --dir packages/protocol-engine check` | 独立构建,以及 Registry、binding、同步、流式、异步任务、校验和 Host 等价性测试。 | 具体 Adapter wire 格式、Host 持久化、真实网络。 |
257
- | 内置 conformance | `pnpm --filter @skyold/provider-adapters test -- src/conformance/runtime-conformance.test.ts` | 每个已注册内置协议族均通过公共 Engine API 和 fixture Transport 执行。 | Provider 凭证或当前远端行为。 |
258
- | 架构边界 | `pnpm --filter @tokenforge/acceptance test -- src/L1-kernel/import-boundary.test.ts` | 引擎依赖隔离、package surface、禁止 raw-forward/直接绕过 Adapter、Host 中立性。 | 单独证明运行时语义正确。 |
259
- | Host 集成 | 各嵌入应用的定向测试与仓库 `pnpm check` | 相应调用方的上下文绑定、流式、媒体持久化、用量与结算兼容。 | exact-SHA 部署或真实 Provider 可用性。 |
260
- | 真实 Provider 认证 | `pnpm certify:protocol-engine-real --manifest /protected/path/suite.json --run --env-file .env --ack-real-provider-calls I_AUTHORIZE_REAL_PROVIDER_CALLS` | 每个成功证据分片对应的当前、已认证 Provider 行为。 | 部署、计费正确性或缺失协议族。 |
259
+ | 层级 | 命令 | 能证明 | 不能证明 |
260
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------- |
261
+ | 引擎 package | `pnpm --dir packages/protocol-engine check` | 独立构建,以及 Registry、binding、同步、流式、异步任务、校验和 Host 等价性测试。 | 具体 Adapter wire 格式、Host 持久化、真实网络。 |
262
+ | 内置 conformance | `pnpm --filter @skyold/provider-adapters test -- src/conformance/runtime-conformance.test.ts` | 每个已注册内置协议族均通过公共 Engine API 和 fixture Transport 执行。 | Provider 凭证或当前远端行为。 |
263
+ | 架构边界 | `pnpm --filter @tokenforge/acceptance test -- src/L1-kernel/import-boundary.test.ts`(在 TokenForge 源 monorepo 中执行) | 引擎依赖隔离、package surface、经 Adapter 显式承载 Provider 响应、Host 中立性。 | 单独证明运行时语义正确。 |
264
+ | Host 集成 | 各嵌入应用的定向测试与仓库 `pnpm check` | 相应调用方的上下文绑定、流式、媒体持久化、用量与结算兼容。 | exact-SHA 部署或真实 Provider 可用性。 |
265
+ | 真实 Provider 认证 | `pnpm certify:protocol-engine-real --manifest /protected/path/suite.json --run --env-file .env --ack-real-provider-calls I_AUTHORIZE_REAL_PROVIDER_CALLS`(在 TokenForge 源 monorepo 中执行) | 每个成功证据分片对应的当前、已认证 Provider 行为。 | 部署、计费正确性或缺失协议族。 |
261
266
 
262
267
  本地修改引擎时,最低独立门禁是引擎 package 命令。协议或 Adapter 变更还必须通过内置 conformance。Host 集成变更还必须通过受影响 Host 的测试。只有 Registry 清单中所有协议族都取得真实 Provider 成功证据,且实际交付的 exact SHA 通过 CI 和部署门禁,整个目标才算完成。
263
268
 
264
269
  ## 真实 Provider 认证
265
270
 
266
- 仓库内的 opt-in 认证 Host 会直接驱动这套公共 Engine API 和内置 Adapter Registry,不经过任何产品应用的 route。版本化 JSON manifest 为 `engine.protocolFamilies` 返回的每个协议族提供一个或多个 case,其中包含已解析 target 与 capability 事实、规范化 task、密钥环境变量名、精确 Provider 域名,以及 suite 请求/时长上限。定价和 Provider 计费刻意不属于引擎契约或认证事实。
271
+ TokenForge 源 monorepo 内的 opt-in 认证 Host 会直接驱动这套公共 Engine API 和内置 Adapter Registry,不经过任何产品应用的 route。本节的认证命令都在该 monorepo 中执行,不在本仓库执行。版本化 JSON manifest 为 `engine.protocolFamilies` 返回的每个协议族提供一个或多个 case,其中包含已解析 target 与 capability 事实、规范化 task、密钥环境变量名、精确 Provider 域名,以及 suite 请求/时长上限。定价和 Provider 计费刻意不属于引擎契约或认证事实。
267
272
 
268
273
  默认只做离线校验:
269
274
 
@@ -286,6 +291,7 @@ pnpm certify:protocol-engine-real --manifest /protected/path/suite.json --run \
286
291
  对于已被接受且支持 list 的异步 target,Acceptance Host 可以通过 `engine.jobs.list` 执行一次只读诊断,不会创建新任务:
287
292
 
288
293
  ```bash
294
+ # 在 TokenForge 源 monorepo(apps/acceptance)中执行,不在本仓库执行。
289
295
  pnpm --filter @tokenforge/acceptance diagnose:protocol-engine-real-async \
290
296
  --manifest /protected/path/async-shard.json \
291
297
  --env-file /protected/path/provider.env \
@@ -298,6 +304,7 @@ pnpm --filter @tokenforge/acceptance diagnose:protocol-engine-real-async \
298
304
  当支持 list 的异步分片已经被 Provider 接受、但认证仅因有界轮询耗尽而停止时,可以恢复这一笔已提交任务,而不创建第二笔任务:
299
305
 
300
306
  ```bash
307
+ # 在 TokenForge 源 monorepo(apps/acceptance)中执行,不在本仓库执行。
301
308
  pnpm --filter @tokenforge/acceptance recover:protocol-engine-real-shard \
302
309
  --manifest /protected/path/async-shard.json \
303
310
  --failure-evidence /protected/path/polling-exhausted.json \
@@ -1,4 +1,4 @@
1
- import type { AdapterTerminalOutcome, NormalizedStreamEventV1, PreparedMediaExecutionInputV2, ProtocolFamily, ProviderAsyncJobListInputV2, ProviderAsyncJobSnapshotV2, ProviderAsyncJobAdapterFactory, ProviderAcceptanceFacts, ProviderAdapterFactory, ProviderDispatchTargetV2, ProviderResponseMetadata, ProviderTransport, TaskEnvelopeV2 } from '@skyold/model-protocol';
1
+ import type { AdapterTerminalOutcome, NormalizedStreamEventV1, PreparedMediaExecutionInputV2, ProtocolFamily, ProviderAsyncJobListInputV2, ProviderAsyncJobSnapshotV2, ProviderAsyncJobAdapterFactory, ProviderAcceptanceFacts, ProviderAdapterFactory, ProviderDispatchTargetV2, ProviderResponseMetadata, ProviderWireResponseV1, ProviderTransport, TaskEnvelopeV2 } from '@skyold/model-protocol';
2
2
  export declare const PROTOCOL_ENGINE_API_VERSION: "tokenforge-protocol-engine/v1";
3
3
  export type ProtocolEngineApiVersion = typeof PROTOCOL_ENGINE_API_VERSION;
4
4
  export type ProtocolEngineDispatchTargetV1 = ProviderDispatchTargetV2;
@@ -22,6 +22,7 @@ interface ProtocolEngineExecutionBaseV1 {
22
22
  readonly apiVersion: ProtocolEngineApiVersion;
23
23
  readonly acceptance: ProviderAcceptanceFacts;
24
24
  readonly responseMetadata: ProviderResponseMetadata;
25
+ readonly providerResponse?: ProviderWireResponseV1;
25
26
  }
26
27
  export interface ProtocolEngineResponseV1 extends ProtocolEngineExecutionBaseV1 {
27
28
  readonly kind: 'response';
@@ -70,11 +71,13 @@ export interface ProtocolEngineAsyncJobObservationV1 {
70
71
  }
71
72
  export interface ProtocolEngineAsyncJobListResultV1 {
72
73
  readonly apiVersion: ProtocolEngineApiVersion;
74
+ readonly providerResponse?: ProviderWireResponseV1;
73
75
  readonly items: readonly ProviderAsyncJobSnapshotV2[];
74
76
  readonly total: number;
75
77
  }
76
78
  export interface ProtocolEngineAsyncJobCancellationV1 {
77
79
  readonly apiVersion: ProtocolEngineApiVersion;
80
+ readonly providerResponse?: ProviderWireResponseV1;
78
81
  }
79
82
  export interface ProtocolEngineAsyncJobsV1 {
80
83
  get(input: ProtocolEngineAsyncJobInputV1): Promise<ProtocolEngineAsyncJobObservationV1>;
package/dist/engine.js CHANGED
@@ -1,4 +1,4 @@
1
- import { providerCapabilityFromTarget, validateProviderDispatchInputV2, validateProviderAsyncJobList, validateProviderAsyncJobSnapshot, validateProviderAsyncJobStart, validateProviderExecutionStart, } from '@skyold/model-protocol';
1
+ import { isProviderWireResponseErrorV1, providerCapabilityFromTarget, validateProviderDispatchInputV2, validateProviderAsyncJobList, validateProviderAsyncJobSnapshot, validateProviderAsyncJobStart, validateProviderExecutionStart, validateProviderWireResponseV1, } from '@skyold/model-protocol';
2
2
  import { PROTOCOL_ENGINE_API_VERSION, } from './contracts.js';
3
3
  import { PROTOCOL_ENGINE_ERROR_CODES_V1, ProtocolEngineErrorV1, isProtocolEngineErrorV1, } from './errors.js';
4
4
  export function createProtocolEngineV1(input) {
@@ -28,14 +28,23 @@ export function createProtocolEngineV1(input) {
28
28
  const result = validateAdapterOutput(() => validateProviderAsyncJobList(adapterResult));
29
29
  return {
30
30
  apiVersion: PROTOCOL_ENGINE_API_VERSION,
31
+ ...(result.providerResponse === undefined
32
+ ? {}
33
+ : { providerResponse: result.providerResponse }),
31
34
  items: result.items,
32
35
  total: result.total,
33
36
  };
34
37
  },
35
38
  async cancel(jobInput) {
36
39
  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 };
40
+ const adapterResult = await callAdapter(() => adapter.cancel(jobInput.providerJobId, jobInput.signal), jobInput.signal);
41
+ const providerResponse = adapterResult === undefined
42
+ ? undefined
43
+ : validateAdapterOutput(() => validateProviderWireResponseV1(adapterResult));
44
+ return {
45
+ apiVersion: PROTOCOL_ENGINE_API_VERSION,
46
+ ...(providerResponse === undefined ? {} : { providerResponse }),
47
+ };
39
48
  },
40
49
  },
41
50
  async execute(executionInput) {
@@ -76,6 +85,9 @@ export function createProtocolEngineV1(input) {
76
85
  kind: 'response',
77
86
  acceptance: started.acceptance,
78
87
  responseMetadata: started.responseMetadata,
88
+ ...(started.providerResponse === undefined
89
+ ? {}
90
+ : { providerResponse: started.providerResponse }),
79
91
  terminal: started.terminal,
80
92
  };
81
93
  return result;
@@ -180,6 +192,9 @@ async function executeAsync(registry, executionInput, binding, capability) {
180
192
  kind: 'accepted-job',
181
193
  acceptance: started.acceptance,
182
194
  responseMetadata: started.responseMetadata,
195
+ ...(started.providerResponse === undefined
196
+ ? {}
197
+ : { providerResponse: started.providerResponse }),
183
198
  providerJobId: started.providerJobId,
184
199
  };
185
200
  }
@@ -188,6 +203,9 @@ async function executeAsync(registry, executionInput, binding, capability) {
188
203
  kind: 'rejected',
189
204
  acceptance: started.acceptance,
190
205
  responseMetadata: started.responseMetadata,
206
+ ...(started.providerResponse === undefined
207
+ ? {}
208
+ : { providerResponse: started.providerResponse }),
191
209
  failureCode: started.failureCode,
192
210
  retryable: started.retryable,
193
211
  };
@@ -298,20 +316,44 @@ function abortAdapter(abort, reason) {
298
316
  }
299
317
  function wrapOperationError(code, cause, signal) {
300
318
  if (isProtocolEngineErrorV1(cause))
301
- return cause;
319
+ return validateProtocolEngineError(cause);
302
320
  if (signal.aborted || (cause instanceof Error && cause.name === 'AbortError')) {
303
321
  return wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ABORTED, cause);
304
322
  }
323
+ if (isProviderWireResponseErrorV1(cause)) {
324
+ try {
325
+ const providerResponse = validateProviderWireResponseV1(cause.providerResponse);
326
+ return new ProtocolEngineErrorV1(code, { cause, providerResponse });
327
+ }
328
+ catch (validationCause) {
329
+ return wrapProtocolEngineError(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID, validationCause, adapterOutputCompatibilityMessage(validationCause));
330
+ }
331
+ }
305
332
  return wrapProtocolEngineError(code, cause);
306
333
  }
307
334
  function wrapProtocolEngineError(code, cause, compatibilityMessage) {
308
335
  if (isProtocolEngineErrorV1(cause))
309
- return cause;
336
+ return validateProtocolEngineError(cause);
310
337
  return new ProtocolEngineErrorV1(code, {
311
338
  cause,
312
339
  ...(compatibilityMessage === undefined ? {} : { compatibilityMessage }),
313
340
  });
314
341
  }
342
+ function validateProtocolEngineError(cause) {
343
+ if (cause.providerResponse === undefined)
344
+ return cause;
345
+ try {
346
+ validateProviderWireResponseV1(cause.providerResponse);
347
+ return cause;
348
+ }
349
+ catch (validationCause) {
350
+ const compatibilityMessage = adapterOutputCompatibilityMessage(validationCause);
351
+ return new ProtocolEngineErrorV1(PROTOCOL_ENGINE_ERROR_CODES_V1.ADAPTER_OUTPUT_INVALID, {
352
+ cause: validationCause,
353
+ ...(compatibilityMessage === undefined ? {} : { compatibilityMessage }),
354
+ });
355
+ }
356
+ }
315
357
  function protocolEngineError(code, compatibilityMessage) {
316
358
  return new ProtocolEngineErrorV1(code, {
317
359
  ...(compatibilityMessage === undefined ? {} : { compatibilityMessage }),
@@ -339,6 +381,7 @@ const ADAPTER_OUTPUT_COMPATIBILITY_MESSAGES = new Set([
339
381
  'ADAPTER_ASYNC_START_INVALID',
340
382
  'ADAPTER_ASYNC_JOB_SNAPSHOT_INVALID',
341
383
  'ADAPTER_ASYNC_JOB_LIST_INVALID',
384
+ 'ADAPTER_PROVIDER_RESPONSE_INVALID',
342
385
  ]);
343
386
  function inputCompatibilityMessage(cause) {
344
387
  return cause instanceof Error && INPUT_COMPATIBILITY_MESSAGES.has(cause.message)
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type ProtocolEngineApiVersion } from './contracts.js';
2
+ import type { ProviderWireResponseV1 } from '@skyold/model-protocol';
2
3
  export declare const PROTOCOL_ENGINE_ERROR_CODES_V1: Readonly<{
3
4
  readonly REGISTRY_INVALID: "PROTOCOL_ENGINE_REGISTRY_INVALID";
4
5
  readonly PROTOCOL_FAMILY_UNAVAILABLE: "PROTOCOL_ENGINE_PROTOCOL_FAMILY_UNAVAILABLE";
@@ -17,6 +18,8 @@ export type ProtocolEngineErrorPhaseV1 = 'configuration' | 'input' | 'transport'
17
18
  export interface ProtocolEngineErrorOptionsV1 {
18
19
  /** Internal diagnostic cause. Hosts must never serialize it to clients. */
19
20
  readonly cause?: unknown;
21
+ /** Complete Provider response available for explicit same-protocol Host egress. */
22
+ readonly providerResponse?: ProviderWireResponseV1;
20
23
  /**
21
24
  * Temporary compatibility message for Hosts that still classify historical
22
25
  * fixed-runtime failures by Error.message. New integrations must use code.
@@ -33,6 +36,7 @@ export declare class ProtocolEngineErrorV1 extends Error {
33
36
  readonly apiVersion: ProtocolEngineApiVersion;
34
37
  readonly code: ProtocolEngineErrorCodeV1;
35
38
  readonly phase: ProtocolEngineErrorPhaseV1;
39
+ readonly providerResponse?: ProviderWireResponseV1;
36
40
  constructor(code: ProtocolEngineErrorCodeV1, options?: ProtocolEngineErrorOptionsV1);
37
41
  }
38
42
  export declare function isProtocolEngineErrorV1(error: unknown): error is ProtocolEngineErrorV1;
package/dist/errors.js CHANGED
@@ -22,11 +22,15 @@ export class ProtocolEngineErrorV1 extends Error {
22
22
  apiVersion = PROTOCOL_ENGINE_API_VERSION;
23
23
  code;
24
24
  phase;
25
+ providerResponse;
25
26
  constructor(code, options = {}) {
26
27
  super(options.compatibilityMessage ?? code, { cause: options.cause });
27
28
  this.name = 'ProtocolEngineErrorV1';
28
29
  this.code = code;
29
30
  this.phase = phaseForCode(code);
31
+ if (options.providerResponse !== undefined) {
32
+ this.providerResponse = options.providerResponse;
33
+ }
30
34
  }
31
35
  }
32
36
  export function isProtocolEngineErrorV1(error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyold/protocol-engine",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0-beta.1",
4
4
  "description": "Host-neutral execution kernel for normalized AI provider protocols.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -50,7 +50,7 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@skyold/model-protocol": "^0.1.0-beta.0"
53
+ "@skyold/model-protocol": "^0.1.0-beta.1"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "22.19.19",