diff --git a/packages/api/gateway/tests/client.spec.ts b/packages/api/gateway/tests/client.spec.ts index a268206d3f..409db48070 100644 --- a/packages/api/gateway/tests/client.spec.ts +++ b/packages/api/gateway/tests/client.spec.ts @@ -5,6 +5,7 @@ import { z } from 'zod' import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client' import type { InvocationDescriptor, + RemoteResult, TypeRTClientRemote, TypeRTContext, TypeRTRemoteScopeApi, @@ -46,16 +47,18 @@ declare module '@deepseek-ai/dsh-type-meta' { agentId: string, request: { readonly objective: string }, signal?: AbortSignal, - ) => Promise<{ readonly ref: string }> - 'probe/maybe': (value: string | null | undefined) => Promise + ) => Promise> + 'probe/maybe': (value: string | null | undefined) => Promise> } interface TypeRTRemoteScopeMap { 'fixture:probe/create': ( request: { readonly objective: string }, signal?: AbortSignal, - ) => Promise<{ readonly ref: string }> - 'fixture:probe/rename': (request: { readonly objective: string }) => Promise<{ readonly renamed: boolean }> + ) => Promise> + 'fixture:probe/rename': ( + request: { readonly objective: string }, + ) => Promise> } interface TypeRTRemoteNamespaceMap { @@ -182,7 +185,8 @@ describe('Client TypeRT API', () => { await assembly const retained = ctx.remote.probe.create - await expect(ctx.remote.probe.create('agent-1', { objective: 'ship' })).resolves.toEqual({ ref: 'goal-1' }) + await expect(ctx.remote.probe.create('agent-1', { objective: 'ship' })) + .resolves.toEqual({ ok: true, value: { ref: 'goal-1' } }) expect(call).toHaveBeenCalledWith( '/api', 'probe/create', @@ -194,7 +198,7 @@ describe('Client TypeRT API', () => { 'agent-1', { objective: 'cancel me' }, callerAbort.signal, - )).resolves.toEqual({ ref: 'goal-1' }) + )).resolves.toEqual({ ok: true, value: { ref: 'goal-1' } }) const combinedSignal = call.mock.calls.at(-1)?.[3] expect(combinedSignal).toBeInstanceOf(AbortSignal) expect(combinedSignal).not.toBe(callerAbort.signal) @@ -205,14 +209,20 @@ describe('Client TypeRT API', () => { await expect(ctx.remote.probe.create('', { objective: 'ship' })).rejects.toThrow('rejected "agentId"') call.mockResolvedValueOnce({ ok: true, value: { ref: 1 } }) - await expect(ctx.remote.probe.create('agent-1', { objective: 'ship' })).rejects.toThrow('rejected "result"') + await expect(ctx.remote.probe.create('agent-1', { objective: 'ship' })).resolves.toMatchObject({ + ok: false, + error: { code: 'internal', message: expect.stringContaining('rejected "result"') }, + }) await assembly.dispose() expect((ctx.remote as unknown as Record).probe).toBeUndefined() expect(ctx.get('remote.probe')).toBeUndefined() expect(ctx.get('probe')).toBe(businessProbe) expect(ctx.typert.remotes.list()).toEqual([]) - await expect(retained?.('agent-1', { objective: 'ship' })).rejects.toThrow('no longer mounted') + await expect(retained?.('agent-1', { objective: 'ship' })).resolves.toMatchObject({ + ok: false, + error: { code: 'internal', message: expect.stringContaining('no longer mounted') }, + }) disposeBusinessProbe() }) @@ -226,7 +236,7 @@ describe('Client TypeRT API', () => { descriptors: [maybeDescriptor()], }) - await expect(ctx.remote.probe.maybe(undefined)).resolves.toBeUndefined() + await expect(ctx.remote.probe.maybe(undefined)).resolves.toStrictEqual({ ok: true, value: undefined }) expect(call).toHaveBeenNthCalledWith( 1, '/api', @@ -234,7 +244,7 @@ describe('Client TypeRT API', () => { { args: {} }, expect.any(AbortSignal), ) - await expect(ctx.remote.probe.maybe(null)).resolves.toBeNull() + await expect(ctx.remote.probe.maybe(null)).resolves.toStrictEqual({ ok: true, value: null }) expect(call).toHaveBeenNthCalledWith( 2, '/api', @@ -260,7 +270,8 @@ describe('Client TypeRT API', () => { )) await assembly - await expect(agentCtx.remote.probe.create({ objective: 'ship scoped' })).resolves.toEqual({ ref: 'goal-2' }) + await expect(agentCtx.remote.probe.create({ objective: 'ship scoped' })) + .resolves.toEqual({ ok: true, value: { ref: 'goal-2' } }) expect(call).toHaveBeenCalledWith( '/api', 'probe/create', @@ -289,7 +300,8 @@ describe('Client TypeRT API', () => { )) await assembly - await expect(agentCtx.remote.probe.rename({ objective: 'land' })).resolves.toEqual({ renamed: true }) + await expect(agentCtx.remote.probe.rename({ objective: 'land' })) + .resolves.toEqual({ ok: true, value: { renamed: true } }) expect(call).toHaveBeenCalledWith( '/api', 'probe/rename', @@ -373,7 +385,8 @@ describe('Client TypeRT API', () => { package: '@fixture/multiple-scoped', descriptors: [directDescriptor(), contextDescriptor()], }) - await expect(agentCtx.remote.probe.rename({ objective: 'remounted' })).resolves.toEqual({ renamed: true }) + await expect(agentCtx.remote.probe.rename({ objective: 'remounted' })) + .resolves.toEqual({ ok: true, value: { renamed: true } }) expect(call).toHaveBeenLastCalledWith( '/api', 'probe/rename', @@ -523,7 +536,10 @@ describe('Client TypeRT API', () => { await dispose() resolveCall({ ok: true, value: { ref: 'goal-1' } }) - await expect(invocation).rejects.toThrow('withdrawn during invocation') + await expect(invocation).resolves.toMatchObject({ + ok: false, + error: { code: 'internal', message: expect.stringContaining('no longer mounted') }, + }) expect((ctx.remote as unknown as Record).probe).toBeUndefined() }) @@ -560,7 +576,7 @@ describe('Client TypeRT API', () => { const dispose = await ctx.remote.$mount({ package: '@fixture/prototype', descriptors: [descriptor] }) const method = (ctx.remote.probe as unknown as Record Promise>).prototype - await expect(method?.('wire-value')).resolves.toEqual({ ref: 'goal-1' }) + await expect(method?.('wire-value')).resolves.toEqual({ ok: true, value: { ref: 'goal-1' } }) const payload = call.mock.calls[0]?.[2] as { readonly args: Record } expect(Object.getPrototypeOf(payload.args)).toBeNull() expect(Object.hasOwn(payload.args, '__proto__')).toBe(true) @@ -649,21 +665,26 @@ describe('Client TypeRT API', () => { await disposeReplacement() }) - it('throws RPC failures with the structured error as its cause', async () => { + it('delivers an RPC failure in the error branch with the Host error verbatim', async () => { const rpcError = { code: 'internal' as const, message: 'host failed', details: {} } const ctx = await bench(vi.fn().mockResolvedValue({ ok: false, error: rpcError })) await ctx.remote.$mount({ package: '@fixture/probe', descriptors: [directDescriptor()] }) - let failure: unknown - try { - await ctx.remote.probe.create('agent-1', { objective: 'ship' }) - } catch (error) { - failure = error - } - expect(failure).toBeInstanceOf(Error) - if (!(failure instanceof Error)) throw new Error('expected Client API invocation to fail') - expect(failure.message).toContain('internal: host failed') - expect(failure.cause).toBe(rpcError) + const outcome = await ctx.remote.probe.create('agent-1', { objective: 'ship' }) + expect(outcome.ok).toBe(false) + if (outcome.ok) throw new Error('expected the Client API invocation to report a failure') + expect(outcome.error).toBe(rpcError) + }) + + it('folds a transport throw into the error branch', async () => { + const ctx = await bench(vi.fn() + .mockRejectedValue(new Error('carrier offline'))) + await ctx.remote.$mount({ package: '@fixture/probe', descriptors: [directDescriptor()] }) + + await expect(ctx.remote.probe.create('agent-1', { objective: 'ship' })).resolves.toMatchObject({ + ok: false, + error: { code: 'internal', message: expect.stringContaining('carrier offline') }, + }) }) it('owns each $on subscription in the calling fiber', async () => { diff --git a/packages/client/runtime/tests/client-apply.spec.ts b/packages/client/runtime/tests/client-apply.spec.ts index 40b7461b37..ae1e20a45b 100644 --- a/packages/client/runtime/tests/client-apply.spec.ts +++ b/packages/client/runtime/tests/client-apply.spec.ts @@ -14,7 +14,7 @@ import type { ConversationNodeDefinition } from '../src/client/contract/conversa import { Session } from '../src/client/sessions/session.ts' import type { SessionsService } from '../src/client/sessions/service.ts' import type { WorkspacesService } from '../src/client/workspaces/service.ts' -import { FakeApiClient, ok } from './fake-api.ts' +import { FakeApiClient, fakeRemote, ok } from './fake-api.ts' interface Bench { ctx: Context @@ -45,6 +45,7 @@ async function mount(): Promise { } ctx.reflect.provide('connection', handle) ctx.reflect.provide('remote', {}) + ctx.reflect.provide('remote.commands', fakeRemote().commands) await ctx.plugin(RuntimeClient).await() return bench } diff --git a/packages/client/runtime/tests/wire-events.spec.ts b/packages/client/runtime/tests/wire-events.spec.ts index 73861b8003..20ff4c966d 100644 --- a/packages/client/runtime/tests/wire-events.spec.ts +++ b/packages/client/runtime/tests/wire-events.spec.ts @@ -13,7 +13,7 @@ import TypertRegistry from '@deepseek-ai/dsh-typert-registry' // key face and per-event listener signatures. import type {} from '@deepseek-ai/dsh-api-remotes/client' import * as RuntimeClient from '../src/client/index.ts' -import { FakeApiClient } from './fake-api.ts' +import { FakeApiClient, fakeRemote } from './fake-api.ts' /** * Compile-time face of `ctx.remote.$on`, asserted by type-checking this file @@ -74,6 +74,7 @@ async function mount(): Promise { }, } ctx.reflect.provide('connection', handle) + ctx.reflect.provide('remote.commands', fakeRemote().commands) await ctx.plugin(RuntimeClient).await() return bench } diff --git a/packages/client/ui-command/tests/browser-plugin.spec.ts b/packages/client/ui-command/tests/browser-plugin.spec.ts index b413e8d79e..617399dab0 100644 --- a/packages/client/ui-command/tests/browser-plugin.spec.ts +++ b/packages/client/ui-command/tests/browser-plugin.spec.ts @@ -33,7 +33,9 @@ async function bench() { scopeOf: (c: Context) => scopeOf(c), }) const commandsRemote = { list: () => Promise.resolve([]) } - ctx.provide('remote', { commands: commandsRemote }) + // The service subscribes its cache-invalidation events on construction, so + // the Remote face needs `$on` even where this spec dispatches none. + ctx.provide('remote', { commands: commandsRemote, $on: () => () => {} }) ctx.provide('remote.commands', commandsRemote) await ctx.plugin(SlotsService).await() ctx.slots.register({ diff --git a/packages/client/ui-command/tests/service.spec.ts b/packages/client/ui-command/tests/service.spec.ts index 861bcdc0aa..c3413317c5 100644 --- a/packages/client/ui-command/tests/service.spec.ts +++ b/packages/client/ui-command/tests/service.spec.ts @@ -40,6 +40,28 @@ interface BenchOptions { addressed?: SessionId } +/** + * Fold one programmed answer into the generated Remote face's outcome: a + * resolved value is the ok branch, a rejection is the transport failure the + * carrier reports in the error branch instead of throwing at the caller. + * @param produce - the scripted answer for one Remote method. + * @returns the carried result the service reads. + */ +async function carried(produce: () => Promise) { + try { + return { ok: true as const, value: await produce() } + } catch (error) { + return { + ok: false as const, + error: { + code: 'internal', + message: error instanceof Error ? error.message : String(error), + details: {}, + }, + } + } +} + async function bench(opts: BenchOptions = {}) { const ctx = new Context() const registered = new Map() @@ -50,21 +72,22 @@ async function bench(opts: BenchOptions = {}) { const commandsRemote = { list: async (sessionId: SessionId) => { listCalls.push({ sessionId }) - const value = await (opts.commands ?? (p => Promise.resolve({ - commands: p.sessionId === sid('s2') ? S2_CMDS : S1_CMDS, - })))({ sessionId }) - return { ok: true as const, value: value.commands } + return await carried(async () => { + const value = await (opts.commands ?? (p => Promise.resolve({ + commands: p.sessionId === sid('s2') ? S2_CMDS : S1_CMDS, + })))({ sessionId }) + return value.commands + }) }, execute: async (sessionId: SessionId, line: string) => { executeCalls.push({ sessionId, line }) - const fallback = (): Promise => Promise.resolve({ matched: true }) - const value = await (opts.execute ?? fallback)({ sessionId, line }) - return { - ok: true as const, - value: value.matched + return await carried(async () => { + const fallback = (): Promise => Promise.resolve({ matched: true }) + const value = await (opts.execute ?? fallback)({ sessionId, line }) + return value.matched ? { commandId: value.commandId ?? 'fake-command', result: { kind: 'success' as const } } - : undefined, - } + : undefined + }) }, } ctx.provide('slash', { diff --git a/packages/client/ui-plan/tests/browser-plugin.spec.ts b/packages/client/ui-plan/tests/browser-plugin.spec.ts index 1a412c06de..c60cf68928 100644 --- a/packages/client/ui-plan/tests/browser-plugin.spec.ts +++ b/packages/client/ui-plan/tests/browser-plugin.spec.ts @@ -26,7 +26,7 @@ async function bench() { children: { 'conversation.input.plan': { kind: 'single', scope: 'session' } }, } as never, () => null) const execute = vi.fn((_sessionId: SessionId, _line: string) => - Promise.resolve({ commandId: 'c1', result: { kind: 'success' as const } })) + Promise.resolve({ ok: true, value: { commandId: 'c1', result: { kind: 'success' as const } } })) const commandsRemote = { execute } ctx.provide('remote', { commands: commandsRemote }) ctx.provide('remote.commands', commandsRemote) @@ -71,14 +71,15 @@ describe('ui-plan browser apply', () => { expect(b.execute).toHaveBeenLastCalledWith(SID, '/plan off') // Business failure folds to the composer-visible line: the generated method - // throws with the RPC failure as its cause. - b.execute.mockRejectedValueOnce(new Error('client api: commands/execute failed', { - cause: { code: 'session-not-found', message: 'gone', details: {} }, - })) + // reports the RPC failure in its error branch. + b.execute.mockResolvedValueOnce({ + ok: false, + error: { code: 'session-not-found', message: 'gone', details: {} }, + } as never) await expect(injected.exitPlanMode()).resolves.toBe('gone (session-not-found)') // Unmatched admission (plan-mode not composed host-side) is also a failure line. - b.execute.mockResolvedValueOnce(undefined as never) + b.execute.mockResolvedValueOnce({ ok: true, value: undefined } as never) await expect(injected.exitPlanMode()).resolves.toBe('unknown command: /plan off') await fiber.dispose() diff --git a/packages/host/directory-picker-auto/tests/loader-composition.spec.ts b/packages/host/directory-picker-auto/tests/loader-composition.spec.ts index ed9bfd1e44..febb49065a 100644 --- a/packages/host/directory-picker-auto/tests/loader-composition.spec.ts +++ b/packages/host/directory-picker-auto/tests/loader-composition.spec.ts @@ -1,10 +1,10 @@ /** * REAL-composition coverage: a test-only cordis.yml booted through the * vendored Loader mounts the webserver row plus the adaptive chooser, and the - * assertions observe the durable outcome — which backend entry the chooser - * mounted into the Loader store, the capability the seam then serves, and - * that disposing the chooser removes the mounted entry again (HMR safety), - * joining the backend's own teardown before the disposer settles. + * assertions observe the durable outcome — which backend and surface entries + * the chooser mounted into the Loader store, the capability the seam then + * serves, and that disposing the chooser removes both mounted entries again + * (HMR safety), joining the backend's own teardown before the disposer settles. */ import { chmodSync, mkdtempSync, writeFileSync } from 'node:fs' @@ -20,6 +20,8 @@ import HttpServer from '@deepseek-ai/dsh-host-webserver' import type { DirectoryPicker } from '@deepseek-ai/dsh-host-directory-picker' import BrowseDirectoryPicker from '@deepseek-ai/dsh-host-directory-picker-browse' import NativeDirectoryPicker from '@deepseek-ai/dsh-host-directory-picker-native' +import * as BrowseSurface from '@deepseek-ai/dsh-client-ui-directory-picker' +import * as NativeSurface from '@deepseek-ai/dsh-client-ui-directory-picker-native' import * as DirectoryPickerAuto from '../src/index.ts' const renameControl = vi.hoisted(() => ({ @@ -48,6 +50,8 @@ vi.mock('node:fs/promises', async (importOriginal) => { const AUTO = '@deepseek-ai/dsh-host-directory-picker-auto' const NATIVE = '@deepseek-ai/dsh-host-directory-picker-native' const BROWSE = '@deepseek-ai/dsh-host-directory-picker-browse' +const NATIVE_SURFACE = '@deepseek-ai/dsh-client-ui-directory-picker-native' +const BROWSE_SURFACE = '@deepseek-ai/dsh-client-ui-directory-picker' let root: string | undefined let fakeBin: string | undefined @@ -92,6 +96,8 @@ async function loadComposition(bindHost: '127.0.0.1' | '0.0.0.0'): Promise<{ ctx [AUTO, DirectoryPickerAuto], [NATIVE, NativeDirectoryPicker], [BROWSE, BrowseDirectoryPicker], + [NATIVE_SURFACE, NativeSurface], + [BROWSE_SURFACE, BrowseSurface], ]) context.loader.internal = { version: 'v2', @@ -142,7 +148,9 @@ describe('real Loader composition', () => { .map(entry => entry.options.name) expect(unloaded).toEqual([]) expect(entryNames(ctx)).toContain(NATIVE) + expect(entryNames(ctx)).toContain(NATIVE_SURFACE) expect(entryNames(ctx)).not.toContain(BROWSE) + expect(entryNames(ctx)).not.toContain(BROWSE_SURFACE) const picker = ctx.get('directoryPicker') as DirectoryPicker expect(picker.capability().kind).toBe('native') // The mounted row lives in the Loader's in-memory root tree only — the @@ -155,6 +163,7 @@ describe('real Loader composition', () => { const autoEntry = [...ctx.loader.entries()].find(entry => entry.options.name === AUTO)! await autoEntry.fiber!.dispose() expect(entryNames(ctx)).not.toContain(NATIVE) + expect(entryNames(ctx)).not.toContain(NATIVE_SURFACE) expect(ctx.get('directoryPicker')).toBeUndefined() // Self-disposing an include-tree entry persists `disabled: true` (loader // behavior, not the chooser's); await that debounced write so it cannot @@ -170,7 +179,9 @@ describe('real Loader composition', () => { const { ctx } = await loadComposition('127.0.0.1') expect(entryNames(ctx)).toContain(BROWSE) + expect(entryNames(ctx)).toContain(BROWSE_SURFACE) expect(entryNames(ctx)).not.toContain(NATIVE) + expect(entryNames(ctx)).not.toContain(NATIVE_SURFACE) const picker = ctx.get('directoryPicker') as DirectoryPicker expect(picker.capability().kind).toBe('browse') }) @@ -180,7 +191,9 @@ describe('real Loader composition', () => { const { ctx } = await loadComposition('0.0.0.0') expect(entryNames(ctx)).toContain(BROWSE) + expect(entryNames(ctx)).toContain(BROWSE_SURFACE) expect(entryNames(ctx)).not.toContain(NATIVE) + expect(entryNames(ctx)).not.toContain(NATIVE_SURFACE) }) it('tolerates the mounted entry being removed by the tree before the chooser unloads', { timeout: 60_000 }, async () => { @@ -193,6 +206,7 @@ describe('real Loader composition', () => { renameControl.remainingFailures = 1 await expect(autoEntry.fiber!.dispose()).resolves.not.toThrow() expect(entryNames(ctx)).not.toContain(NATIVE) + expect(entryNames(ctx)).not.toContain(NATIVE_SURFACE) // Same self-dispose persistence as above: let the write land before teardown. await expect.poll(async () => await readFile(configPath, 'utf8')).toContain('disabled: true') expect(renameControl.injectedFailures).toBe(1) diff --git a/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts b/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts index 707dc84ce9..7fbc5bd656 100644 --- a/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts +++ b/packages/typert/generator/tests/fixtures/remote-model/type-meta.d.ts @@ -13,6 +13,16 @@ declare module '@deepseek-ai/dsh-type-meta' { export interface TypeRTRemoteMap {} export interface TypeRTRemoteScopeMap {} + export interface RemoteFailure { + readonly code: string + readonly message: string + readonly details: object + } + + export type RemoteResult = + | { readonly ok: true; readonly value: T } + | { readonly ok: false; readonly error: RemoteFailure } + export type TypeRTRemoteNamespace = { [Endpoint in keyof TypeRTRemoteMap as Endpoint extends `${Namespace}/${infer Method}` ? Method diff --git a/packages/typert/generator/tests/remote-model.spec.ts b/packages/typert/generator/tests/remote-model.spec.ts index 567feaae4e..443d5997c3 100644 --- a/packages/typert/generator/tests/remote-model.spec.ts +++ b/packages/typert/generator/tests/remote-model.spec.ts @@ -114,15 +114,15 @@ describe('Remote model generation', { timeout: 60_000 }, () => { expect(artifact?.js).toContain('invocations: [') expect(artifact?.remote?.dts).toContain( - "'goals/create': (agentId: AgentId, request: CreateGoalRequest, signal?: AbortSignal) => Promise", + "'goals/create': (agentId: AgentId, request: CreateGoalRequest, signal?: AbortSignal) => Promise>", ) expect(artifact?.remote?.dts).toContain('interface TypeRTRemoteNamespace$676f616c73 {\n create:') expect(artifact?.remote?.dts).toContain("'goals': TypeRTRemoteNamespace$676f616c73") expect(artifact?.remote?.dts).toContain( - "'agent:goals/create': (request: CreateGoalRequest, signal?: AbortSignal) => Promise", + "'agent:goals/create': (request: CreateGoalRequest, signal?: AbortSignal) => Promise>", ) expect(artifact?.remote?.dts).toContain( - "'agent:goals/rename': (request: RenameGoalRequest) => Promise", + "'agent:goals/rename': (request: RenameGoalRequest) => Promise>", ) const remoteJs = artifact?.remote?.js @@ -172,13 +172,13 @@ export type {`, const [artifact] = new WorkspaceTypertGenerator(root).generate() expect(artifact?.remote?.dts).toContain( - "'goals/maybe': (value: string | undefined) => Promise", + "'goals/maybe': (value: string | undefined) => Promise>", ) - expect(artifact?.remote?.dts).toContain("'goals/clear': () => Promise") + expect(artifact?.remote?.dts).toContain("'goals/clear': () => Promise>") // An explicit `T | undefined` stays a required argument; only authored // optionality lets a consumer omit the field. expect(artifact?.remote?.dts).not.toContain('value?: string') - expect(artifact?.remote?.dts).toContain("'goals/labelled': (id: string, label?: string) => Promise") + expect(artifact?.remote?.dts).toContain("'goals/labelled': (id: string, label?: string) => Promise>") const remoteJs = artifact?.remote?.js if (remoteJs === undefined) throw new Error('undefined Remote fixture emitted no Host-for-Client JavaScript') @@ -256,7 +256,7 @@ export type GenericResult = { const [artifact] = new WorkspaceTypertGenerator(root).generate() expect(artifact?.remote?.dts).toContain( - "'goals/dispatch': (request: GenericRequest) => Promise", + "'goals/dispatch': (request: GenericRequest) => Promise>", ) const remoteJs = artifact?.remote?.js if (remoteJs === undefined) throw new Error('generic Remote fixture emitted no Host-for-Client JavaScript') @@ -306,7 +306,7 @@ export interface BoxPayload { const [artifact] = new WorkspaceTypertGenerator(root).generate() expect(artifact?.remote?.dts).toMatch(/import type \{ [^}]*Box[^}]*BoxPayload[^}]* \} from '@fixture\/remote\/types'/) - expect(artifact?.remote?.dts).toContain('box: (request: Box) => Promise>') + expect(artifact?.remote?.dts).toContain('box: (request: Box) => Promise>>') assertRemoteConsumerTypechecks(artifact?.remote?.dts, artifact?.remote?.dtsMap, root) }) @@ -326,7 +326,7 @@ export interface BoxPayload { )) const [artifact] = new WorkspaceTypertGenerator(root).generate() - expect(artifact?.remote?.dts).toContain("'create-goal': (request: CreateGoalRequest) => Promise") + expect(artifact?.remote?.dts).toContain("'create-goal': (request: CreateGoalRequest) => Promise>") assertRemoteConsumerTypechecks(artifact?.remote?.dts, artifact?.remote?.dtsMap, root) }) @@ -636,6 +636,7 @@ function assertRemoteConsumerTypechecks( const consumerSource = ` import remote from '@fixture/remote/remote' import type { + RemoteResult, TypeRTRemoteContribution, TypeRTRemoteScopeMap, TypeRTRemoteMap, @@ -647,12 +648,12 @@ const contribution: TypeRTRemoteContribution = remote declare const create: TypeRTRemoteMap['goals/create'] declare const createScoped: TypeRTRemoteScopeMap['agent:goals/create'] declare const rename: TypeRTRemoteScopeMap['agent:goals/rename'] -const created: Promise = create('agent-1', { title: 'ship' }) -const cancellable: Promise = create('agent-1', { title: 'ship' }, new AbortController().signal) -const createdScoped: Promise = createScoped({ title: 'ship' }) -const renamed: Promise = rename({ ref: 'goal-1', title: 'land' }) +const created: Promise> = create('agent-1', { title: 'ship' }) +const cancellable: Promise> = create('agent-1', { title: 'ship' }, new AbortController().signal) +const createdScoped: Promise> = createScoped({ title: 'ship' }) +const renamed: Promise> = rename({ ref: 'goal-1', title: 'land' }) declare const ctx: { remote: TypeRTRemoteNamespaceMap } -const navigated: Promise = ctx.remote.goals.create('agent-1', { title: 'navigate' }) +const navigated: Promise> = ctx.remote.goals.create('agent-1', { title: 'navigate' }) void contribution void created void cancellable