fix(invariants): assert runtime relationships, not API shapes
This commit is contained in:
@@ -1,27 +1,24 @@
|
||||
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-llm-deepseek`. @module @deepseek-ai/dsh-llm-deepseek/invariant */
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-llm-deepseek`.
|
||||
* @module @deepseek-ai/dsh-llm-deepseek/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from 'cordis'
|
||||
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-llm-deepseek'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'llm-deepseek-invariant'
|
||||
/** Services required before the companion can register. */
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Install checks for this package's active plugin fibers. */
|
||||
const install: InvariantInstaller = (ctx, fail) => {
|
||||
observePluginInvariant(ctx, fail, {
|
||||
name: 'llm-deepseek',
|
||||
inject: [
|
||||
'llm',
|
||||
],
|
||||
effects: [
|
||||
'llm.registerAdapter()',
|
||||
],
|
||||
})
|
||||
}
|
||||
/**
|
||||
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
||||
* beyond contracts enforced at its owning seam.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
@@ -30,3 +27,4 @@ const install: InvariantInstaller = (ctx, fail) => {
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-llm-pi-ai`. @module @deepseek-ai/dsh-llm-pi-ai/invariant */
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-llm-pi-ai`.
|
||||
* @module @deepseek-ai/dsh-llm-pi-ai/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from 'cordis'
|
||||
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-llm-pi-ai'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'llm-pi-ai-invariant'
|
||||
/** Services required before the companion can register. */
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Install checks for this package's active plugin fibers. */
|
||||
const install: InvariantInstaller = (ctx, fail) => {
|
||||
observePluginInvariant(ctx, fail, {
|
||||
name: 'llm-pi-ai',
|
||||
inject: [
|
||||
'llm',
|
||||
],
|
||||
effects: [
|
||||
'llm.registerAdapter()',
|
||||
],
|
||||
})
|
||||
}
|
||||
/**
|
||||
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
||||
* beyond contracts enforced at its owning seam.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
@@ -30,3 +27,4 @@ const install: InvariantInstaller = (ctx, fail) => {
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
@@ -1,30 +1,93 @@
|
||||
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-llm`. @module @deepseek-ai/dsh-llm/invariant */
|
||||
/** Package-owned LLM stream-protocol invariants. @module @deepseek-ai/dsh-llm/invariant */
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { ContentBlockType, StreamChunk } from './types.ts'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'llm-invariant'
|
||||
/** Services required before the companion can register. */
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Install checks for this package's active plugin fibers. */
|
||||
/** Require one chunk index to be a non-negative safe integer. */
|
||||
function validateIndex(index: number, fail: InvariantFailure): void {
|
||||
if (!Number.isSafeInteger(index) || index < 0) {
|
||||
fail(`LLM stream block index must be a non-negative safe integer, got ${index}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Require a delta to address an open block of its matching type. */
|
||||
function validateDelta(
|
||||
open: ReadonlyMap<number, ContentBlockType>,
|
||||
index: number,
|
||||
expected: ContentBlockType,
|
||||
fail: InvariantFailure,
|
||||
): void {
|
||||
validateIndex(index, fail)
|
||||
const actual = open.get(index)
|
||||
if (actual !== expected) {
|
||||
fail(`${expected} delta at index ${index} requires an open ${expected} block, got ${String(actual)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrap one provider stream and enforce its grammar as chunks are consumed. */
|
||||
async function* validateStream(
|
||||
source: AsyncIterable<StreamChunk>,
|
||||
fail: InvariantFailure,
|
||||
): AsyncIterable<StreamChunk> {
|
||||
const open = new Map<number, ContentBlockType>()
|
||||
let usageSeen = false
|
||||
let finished = false
|
||||
for await (const chunk of source) {
|
||||
if (finished) fail(`LLM stream emitted ${chunk.type} after terminal finish`)
|
||||
switch (chunk.type) {
|
||||
case 'block-start':
|
||||
validateIndex(chunk.index, fail)
|
||||
if (open.has(chunk.index)) fail(`LLM stream repeated block-start index ${chunk.index}`)
|
||||
open.set(chunk.index, chunk.blockType)
|
||||
break
|
||||
case 'text-delta':
|
||||
validateDelta(open, chunk.index, 'text', fail)
|
||||
break
|
||||
case 'reasoning-delta':
|
||||
validateDelta(open, chunk.index, 'reasoning', fail)
|
||||
break
|
||||
case 'tool-call-delta':
|
||||
validateDelta(open, chunk.index, 'tool-call', fail)
|
||||
break
|
||||
case 'block-end': {
|
||||
validateIndex(chunk.index, fail)
|
||||
const blockType = open.get(chunk.index)
|
||||
if (blockType === undefined) fail(`LLM stream block-end index ${chunk.index} has no open block`)
|
||||
if (chunk.block.type !== blockType) {
|
||||
fail(`LLM stream block-end index ${chunk.index} closes ${chunk.block.type}, expected ${blockType}`)
|
||||
}
|
||||
open.delete(chunk.index)
|
||||
break
|
||||
}
|
||||
case 'usage':
|
||||
if (usageSeen) fail('LLM stream emitted usage more than once')
|
||||
usageSeen = true
|
||||
break
|
||||
case 'finish':
|
||||
if (open.size > 0) fail(`LLM stream finished with ${open.size} open block(s)`)
|
||||
finished = true
|
||||
break
|
||||
}
|
||||
yield chunk
|
||||
}
|
||||
if (!finished) fail('LLM stream ended without a terminal finish chunk')
|
||||
}
|
||||
|
||||
/** Install validation around every provider stream. */
|
||||
const install: InvariantInstaller = (ctx, fail) => {
|
||||
observePluginInvariant(ctx, fail, {
|
||||
name: 'LlmService',
|
||||
effects: [
|
||||
'ctx.provide("llm")',
|
||||
],
|
||||
services: [
|
||||
'llm',
|
||||
],
|
||||
})
|
||||
ctx.on('llm/stream', (_options, next) => validateStream(next(), fail), { global: true, prepend: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
* Register the LLM invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns the installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
|
||||
86
packages/llm/llm/tests/invariant.spec.ts
Normal file
86
packages/llm/llm/tests/invariant.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import * as LlmInvariant from '@deepseek-ai/dsh-llm/invariant'
|
||||
import InvariantService from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
async function setup(): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(InvariantService)
|
||||
await ctx.plugin(LlmInvariant)
|
||||
return ctx
|
||||
}
|
||||
|
||||
const options: GenerateOptions = { provider: 'mock', model: 'mock', messages: [] }
|
||||
|
||||
async function* source(chunks: readonly StreamChunk[]): AsyncIterable<StreamChunk> {
|
||||
yield* chunks
|
||||
}
|
||||
|
||||
async function consume(ctx: Context, chunks: readonly StreamChunk[]): Promise<StreamChunk[]> {
|
||||
const stream = ctx.waterfall(ctx as never, 'llm/stream', options, () => source(chunks))
|
||||
const consumed: StreamChunk[] = []
|
||||
for await (const chunk of stream) consumed.push(chunk)
|
||||
return consumed
|
||||
}
|
||||
|
||||
const finish: StreamChunk = { type: 'finish', reason: { kind: 'stop' } }
|
||||
|
||||
describe('LLM stream invariants', () => {
|
||||
it('accepts a complete interleaved stream grammar', async () => {
|
||||
const ctx = await setup()
|
||||
const chunks: StreamChunk[] = [
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
{ type: 'text-delta', index: 0, text: 'a' },
|
||||
{ type: 'block-start', index: 1, blockType: 'reasoning' },
|
||||
{ type: 'reasoning-delta', index: 1, text: 'b' },
|
||||
{ type: 'block-end', index: 1, block: { type: 'reasoning', text: 'b' } },
|
||||
{ type: 'block-end', index: 0, block: { type: 'text', text: 'a' } },
|
||||
{ type: 'block-start', index: 2, blockType: 'tool-call' },
|
||||
{ type: 'tool-call-delta', index: 2, id: CallId('c1'), name: 'echo', argumentsDelta: '{}' },
|
||||
{ type: 'block-end', index: 2, block: { type: 'tool-call', id: CallId('c1'), name: 'echo', arguments: '{}' } },
|
||||
{ type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } },
|
||||
finish,
|
||||
]
|
||||
await expect(consume(ctx, chunks)).resolves.toEqual(chunks)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[[{ type: 'block-start', index: -1, blockType: 'text' }, finish], /non-negative safe integer/],
|
||||
[[
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
], /repeated block-start/],
|
||||
[[{ type: 'text-delta', index: 0, text: 'x' }], /requires an open text block/],
|
||||
[[
|
||||
{ type: 'block-start', index: 0, blockType: 'reasoning' },
|
||||
{ type: 'text-delta', index: 0, text: 'x' },
|
||||
], /got reasoning/],
|
||||
[[{ type: 'block-end', index: 0, block: { type: 'text', text: '' } }], /has no open block/],
|
||||
[[
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'reasoning', text: '' } },
|
||||
], /closes reasoning, expected text/],
|
||||
[[
|
||||
{ type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } },
|
||||
{ type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } },
|
||||
], /usage more than once/],
|
||||
[[{ type: 'block-start', index: 0, blockType: 'text' }, finish], /finished with 1 open block/],
|
||||
[[finish, { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }], /usage after terminal finish/],
|
||||
[[], /ended without a terminal finish/],
|
||||
] as Array<[StreamChunk[], RegExp]>)('rejects malformed stream %#', async (chunks, message) => {
|
||||
const ctx = await setup()
|
||||
await expect(consume(ctx, chunks)).rejects.toThrow(message)
|
||||
})
|
||||
|
||||
it('preserves provider exceptions without inventing a missing-finish failure', async () => {
|
||||
const ctx = await setup()
|
||||
const stream = ctx.waterfall(ctx as never, 'llm/stream', options, async function* () {
|
||||
throw new Error('provider failed')
|
||||
})
|
||||
await expect((async () => {
|
||||
for await (const _chunk of stream) { /* consume */ }
|
||||
})()).rejects.toThrow('provider failed')
|
||||
})
|
||||
})
|
||||
@@ -60,6 +60,7 @@ class CatalogAdapter extends ScriptedAdapter {
|
||||
const SCRIPT: StreamChunk[] = [
|
||||
{ type: 'block-start', index: 0, blockType: 'text' },
|
||||
{ type: 'text-delta', index: 0, text: 'hi' },
|
||||
{ type: 'block-end', index: 0, block: { type: 'text', text: 'hi' } },
|
||||
{ type: 'finish', reason: { kind: 'stop' } },
|
||||
]
|
||||
|
||||
@@ -489,13 +490,14 @@ describe('LlmService', () => {
|
||||
const inner = next()
|
||||
return (async function * () {
|
||||
yield { type: 'block-start', index: 99, blockType: 'text' } satisfies StreamChunk
|
||||
yield { type: 'block-end', index: 99, block: { type: 'text', text: '' } } satisfies StreamChunk
|
||||
yield * inner
|
||||
})()
|
||||
})
|
||||
|
||||
const chunks: StreamChunk[] = []
|
||||
for await (const chunk of ctx.llm.stream({ provider: 'test-model', model: 'dynamic-model', messages: [] })) chunks.push(chunk)
|
||||
expect(chunks).toHaveLength(4)
|
||||
expect(chunks).toHaveLength(6)
|
||||
expect(chunks[0]).toMatchObject({ index: 99 })
|
||||
})
|
||||
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
/** Package-owned runtime contract checks for `@deepseek-ai/dsh-token-meter`. @module @deepseek-ai/dsh-token-meter/invariant */
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-token-meter`.
|
||||
* @module @deepseek-ai/dsh-token-meter/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from 'cordis'
|
||||
import { observePluginInvariant, type InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'token-meter-invariant'
|
||||
/** Services required before the companion can register. */
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Install checks for this package's active plugin fibers. */
|
||||
const install: InvariantInstaller = (ctx, fail) => {
|
||||
observePluginInvariant(ctx, fail, {
|
||||
name: 'TokenMeterService',
|
||||
effects: [
|
||||
'ctx.provide("tokenMeter")',
|
||||
'ctx.on("session/event")',
|
||||
],
|
||||
services: [
|
||||
'tokenMeter',
|
||||
],
|
||||
})
|
||||
}
|
||||
/**
|
||||
* No runtime invariant: token estimates are per-call outputs and the private session cache is
|
||||
* invalidated at its event mutation boundary; neither exposes an independent observation stream.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
@@ -31,3 +27,4 @@ const install: InvariantInstaller = (ctx, fail) => {
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
Reference in New Issue
Block a user