Download packages/node-sdk/test/session-event-wiring.test.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 6.8 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/node-sdk/test/session-event-wiring.test.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/node-sdk/test/session-event-wiring.test.ts
-
curl -L -o session-event-wiring.test.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/node-sdk/test/session-event-wiring.test.ts
6.8 kB
| /** | |
| * `SessionEventWiring` — the in-process v1 edge over the v2 per-agent event | |
| * bus. Covers the status-snapshot fold: v2 emits `agent.status.updated` in | |
| * slices and the model slice rides only the bind-time emission, so the | |
| * wiring merges a consistent usage + context + model snapshot into every | |
| * status event (mirrors kap-server's broadcaster bridge). | |
| * Run: pnpm exec vitest run test/session-event-wiring.test.ts | |
| */ | |
| import { describe, expect, it } from 'vitest'; | |
| import type { Event } from '#/index'; | |
| import { | |
| IAgentLifecycleService, | |
| IAgentProfileService, | |
| IAgentScopeContext, | |
| IEventBus, | |
| ISessionTokenCountingService, | |
| ISessionUsageService, | |
| makeAgentScopeContext, | |
| type ISessionScopeHandle, | |
| } from '@moonshot-ai/agent-core-v2'; | |
| import { SessionEventWiring, type SessionEventSink } from '#/v2/session-wiring'; | |
| // --------------------------------------------------------------------------- | |
| // Fakes | |
| // --------------------------------------------------------------------------- | |
| type FakeBusEvent = { type: string } & Record<string, unknown>; | |
| class FakeAgentBus { | |
| private handlers: Array<(e: FakeBusEvent) => void> = []; | |
| subscribe(handler: (e: FakeBusEvent) => void): { dispose(): void } { | |
| this.handlers.push(handler); | |
| return { | |
| dispose: () => { | |
| const i = this.handlers.indexOf(handler); | |
| if (i >= 0) this.handlers.splice(i, 1); | |
| }, | |
| }; | |
| } | |
| emit(e: FakeBusEvent): void { | |
| for (const h of [...this.handlers]) h(e); | |
| } | |
| } | |
| class FakeAgentHandle { | |
| readonly kind = 2; | |
| readonly bus = new FakeAgentBus(); | |
| readonly accessor; | |
| readonly context; | |
| private readonly services = new Map<unknown, unknown>(); | |
| constructor(readonly id: string) { | |
| const scopeContext = makeAgentScopeContext({ agentId: id, agentScope: `agents/${id}` }); | |
| this.context = scopeContext.agentContext; | |
| this.services.set(IAgentScopeContext, scopeContext); | |
| this.services.set(IEventBus, this.bus); | |
| this.accessor = { | |
| get: (token: unknown) => this.services.get(token), | |
| }; | |
| } | |
| set(token: unknown, service: unknown): void { | |
| this.services.set(token, service); | |
| } | |
| dispose(): void {} | |
| } | |
| function makeSession(agents: FakeAgentHandle[]): ISessionScopeHandle { | |
| const lifecycle = { | |
| list: () => agents.map((agent) => agent.context), | |
| get: (agentId: string) => agents.find((agent) => agent.id === agentId)?.context, | |
| handleOf: (agentId: string) => agents.find((agent) => agent.id === agentId), | |
| onDidCreate: () => ({ dispose: () => {} }), | |
| onDidClose: () => ({ dispose: () => {} }), | |
| }; | |
| const accessor = { | |
| get: (token: unknown): unknown => { | |
| if (token === IAgentLifecycleService) return lifecycle; | |
| return undefined; | |
| }, | |
| }; | |
| return { id: 's1', kind: 1, accessor, dispose: () => {} } as unknown as ISessionScopeHandle; | |
| } | |
| function collectingSink(): { sink: SessionEventSink; events: Event[] } { | |
| const events: Event[] = []; | |
| return { | |
| events, | |
| sink: { | |
| receiveEvent: (event) => { | |
| events.push(event); | |
| }, | |
| requestApproval: () => Promise.resolve('cancelled' as never), | |
| requestQuestion: () => Promise.resolve(null), | |
| toolCall: () => Promise.resolve({ output: 'not supported', isError: true }), | |
| }, | |
| }; | |
| } | |
| const USAGE = { | |
| total: { inputOther: 1, output: 2, inputCacheRead: 0, inputCacheCreation: 0 }, | |
| }; | |
| function bindStatusServices(agent: FakeAgentHandle, model: string): void { | |
| agent.set(ISessionTokenCountingService, { statusSize: () => 10 }); | |
| agent.set(IAgentProfileService, { | |
| getModel: () => model, | |
| getModelCapabilities: () => ({ max_context_tokens: 128_000 }), | |
| }); | |
| agent.set(ISessionUsageService, { status: () => USAGE }); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Tests | |
| // --------------------------------------------------------------------------- | |
| describe('SessionEventWiring status snapshot fold', () => { | |
| it('folds a consistent usage + context + model snapshot into every status event', () => { | |
| const sub = new FakeAgentHandle('agent-1'); | |
| bindStatusServices(sub, 'sub-model'); | |
| const { sink, events } = collectingSink(); | |
| const wiring = new SessionEventWiring(makeSession([sub]), sink); | |
| try { | |
| // The v2 model slice rides only the subagent's bind-time emission, which | |
| // reaches clients before `subagent.spawned` and is dropped there; a | |
| // later usage-only slice must still carry the model at this edge. | |
| sub.bus.emit({ type: 'agent.status.updated', usage: USAGE }); | |
| // Non-status events pass through untouched. | |
| sub.bus.emit({ type: 'assistant.delta', delta: 'Hi', time: 1_700_000_000_123 }); | |
| } finally { | |
| wiring.dispose(); | |
| } | |
| expect(events).toHaveLength(2); | |
| expect(events[0]).toMatchObject({ | |
| type: 'agent.status.updated', | |
| sessionId: 's1', | |
| agentId: 'agent-1', | |
| usage: USAGE, | |
| contextTokens: 10, | |
| maxContextTokens: 128_000, | |
| contextUsage: 10 / 128_000, | |
| model: 'sub-model', | |
| }); | |
| expect(events[1]).toMatchObject({ | |
| type: 'assistant.delta', | |
| delta: 'Hi', | |
| time: 1_700_000_000_123, | |
| }); | |
| expect(events[1]).not.toHaveProperty('model'); | |
| }); | |
| it('passes status events through unchanged when the agent services are incomplete', () => { | |
| const sub = new FakeAgentHandle('agent-1'); | |
| // No profile/usage/context/wire services bound — nothing to fold in. | |
| const { sink, events } = collectingSink(); | |
| const wiring = new SessionEventWiring(makeSession([sub]), sink); | |
| try { | |
| sub.bus.emit({ type: 'agent.status.updated', usage: USAGE }); | |
| } finally { | |
| wiring.dispose(); | |
| } | |
| expect(events).toHaveLength(1); | |
| expect(events[0]).toMatchObject({ type: 'agent.status.updated', usage: USAGE }); | |
| expect(events[0]).not.toHaveProperty('model'); | |
| }); | |
| it('strips the internal promptAttachments field from turn.started', () => { | |
| const sub = new FakeAgentHandle('agent-1'); | |
| const { sink, events } = collectingSink(); | |
| const wiring = new SessionEventWiring(makeSession([sub]), sink); | |
| try { | |
| // `promptAttachments` is transcript-projection metadata: kap-server | |
| // strips it from the WS wire event, so SDK consumers must not see it | |
| // either. | |
| sub.bus.emit({ | |
| type: 'turn.started', | |
| turnId: 1, | |
| origin: { kind: 'user' }, | |
| prompt: 'describe this', | |
| promptAttachments: [{ kind: 'image', fileId: 'f_1' }], | |
| }); | |
| } finally { | |
| wiring.dispose(); | |
| } | |
| expect(events).toHaveLength(1); | |
| expect(events[0]).toMatchObject({ | |
| type: 'turn.started', | |
| turnId: 1, | |
| sessionId: 's1', | |
| agentId: 'agent-1', | |
| prompt: 'describe this', | |
| }); | |
| expect(events[0]).not.toHaveProperty('promptAttachments'); | |
| }); | |
| }); | |