Skip to content

Commit 30f69db

Browse files
Copilothotlong
andauthored
fix: align getDiscovery capabilities with DiscoverySchema hierarchical format
The DiscoverySchema.capabilities expects Record<string, { enabled: boolean }> (hierarchical format), but ObjectStackProtocolImplementation.getDiscovery() was returning WellKnownCapabilities (flat booleans), causing TS2416. - Convert flat WellKnownCapabilities booleans to { enabled: boolean } objects in protocol.ts getDiscovery() - Update discovery tests to expect hierarchical format - Update client capabilities getter to normalize both hierarchical and flat formats back to WellKnownCapabilities for backward compatibility Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/069cea55-358d-4b17-b1b8-505aaa03ccb6 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 9b40ee7 commit 30f69db

3 files changed

Lines changed: 46 additions & 27 deletions

File tree

packages/client/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,20 @@ export class ObjectStackClient {
312312
* Well-known capability flags discovered from the server.
313313
* Returns undefined if the client has not yet connected or the server
314314
* did not include capabilities in its discovery response.
315+
*
316+
* The server may return capabilities in hierarchical format
317+
* `{ key: { enabled: boolean } }` or flat boolean format `{ key: boolean }`.
318+
* This getter normalizes both to flat `WellKnownCapabilities`.
315319
*/
316320
get capabilities(): WellKnownCapabilities | undefined {
317-
return this.discoveryInfo?.capabilities;
321+
const raw = this.discoveryInfo?.capabilities;
322+
if (!raw) return undefined;
323+
// Normalize: hierarchical { enabled: boolean } → flat boolean
324+
const result: Record<string, boolean> = {};
325+
for (const [key, value] of Object.entries(raw)) {
326+
result[key] = typeof value === 'object' && value !== null ? !!(value as any).enabled : !!value;
327+
}
328+
return result as unknown as WellKnownCapabilities;
318329
}
319330

320331
/**

packages/objectql/src/protocol-discovery.test.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -148,28 +148,28 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
148148
expect(discovery.capabilities).toBeDefined();
149149
// workflow is registered but doesn't map to a well-known capability directly
150150
expect(discovery.services.workflow.enabled).toBe(true);
151-
// All well-known capabilities should be false since workflow doesn't map to any
152-
expect(discovery.capabilities!.feed).toBe(false);
153-
expect(discovery.capabilities!.comments).toBe(false);
154-
expect(discovery.capabilities!.automation).toBe(false);
155-
expect(discovery.capabilities!.cron).toBe(false);
156-
expect(discovery.capabilities!.search).toBe(false);
157-
expect(discovery.capabilities!.export).toBe(false);
158-
expect(discovery.capabilities!.chunkedUpload).toBe(false);
151+
// All well-known capabilities should be disabled since workflow doesn't map to any
152+
expect(discovery.capabilities!.feed).toEqual({ enabled: false });
153+
expect(discovery.capabilities!.comments).toEqual({ enabled: false });
154+
expect(discovery.capabilities!.automation).toEqual({ enabled: false });
155+
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
156+
expect(discovery.capabilities!.search).toEqual({ enabled: false });
157+
expect(discovery.capabilities!.export).toEqual({ enabled: false });
158+
expect(discovery.capabilities!.chunkedUpload).toEqual({ enabled: false });
159159
});
160160

161161
it('should set all capabilities to false when no services are registered', async () => {
162162
protocol = new ObjectStackProtocolImplementation(engine);
163163
const discovery = await protocol.getDiscovery();
164164

165165
expect(discovery.capabilities).toBeDefined();
166-
expect(discovery.capabilities!.feed).toBe(false);
167-
expect(discovery.capabilities!.comments).toBe(false);
168-
expect(discovery.capabilities!.automation).toBe(false);
169-
expect(discovery.capabilities!.cron).toBe(false);
170-
expect(discovery.capabilities!.search).toBe(false);
171-
expect(discovery.capabilities!.export).toBe(false);
172-
expect(discovery.capabilities!.chunkedUpload).toBe(false);
166+
expect(discovery.capabilities!.feed).toEqual({ enabled: false });
167+
expect(discovery.capabilities!.comments).toEqual({ enabled: false });
168+
expect(discovery.capabilities!.automation).toEqual({ enabled: false });
169+
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
170+
expect(discovery.capabilities!.search).toEqual({ enabled: false });
171+
expect(discovery.capabilities!.export).toEqual({ enabled: false });
172+
expect(discovery.capabilities!.chunkedUpload).toEqual({ enabled: false });
173173
});
174174

175175
it('should dynamically set capabilities based on registered services', async () => {
@@ -182,13 +182,13 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
182182
protocol = new ObjectStackProtocolImplementation(engine, () => mockServices);
183183
const discovery = await protocol.getDiscovery();
184184

185-
expect(discovery.capabilities!.feed).toBe(true);
186-
expect(discovery.capabilities!.comments).toBe(true);
187-
expect(discovery.capabilities!.automation).toBe(true);
188-
expect(discovery.capabilities!.cron).toBe(false);
189-
expect(discovery.capabilities!.search).toBe(true);
190-
expect(discovery.capabilities!.export).toBe(true);
191-
expect(discovery.capabilities!.chunkedUpload).toBe(true);
185+
expect(discovery.capabilities!.feed).toEqual({ enabled: true });
186+
expect(discovery.capabilities!.comments).toEqual({ enabled: true });
187+
expect(discovery.capabilities!.automation).toEqual({ enabled: true });
188+
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
189+
expect(discovery.capabilities!.search).toEqual({ enabled: true });
190+
expect(discovery.capabilities!.export).toEqual({ enabled: true });
191+
expect(discovery.capabilities!.chunkedUpload).toEqual({ enabled: true });
192192
});
193193

194194
it('should enable cron capability when job service is registered', async () => {
@@ -198,7 +198,7 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
198198
protocol = new ObjectStackProtocolImplementation(engine, () => mockServices);
199199
const discovery = await protocol.getDiscovery();
200200

201-
expect(discovery.capabilities!.cron).toBe(true);
201+
expect(discovery.capabilities!.cron).toEqual({ enabled: true });
202202
});
203203

204204
it('should enable export capability when queue service is registered', async () => {
@@ -208,6 +208,6 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
208208
protocol = new ObjectStackProtocolImplementation(engine, () => mockServices);
209209
const discovery = await protocol.getDiscovery();
210210

211-
expect(discovery.capabilities!.export).toBe(true);
211+
expect(discovery.capabilities!.export).toEqual({ enabled: true });
212212
});
213213
});

packages/objectql/src/protocol.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
151151
...optionalRoutes,
152152
};
153153

154-
// Build well-known capabilities from registered services
155-
const capabilities: WellKnownCapabilities = {
154+
// Build well-known capabilities from registered services.
155+
// DiscoverySchema defines capabilities as Record<string, { enabled, features?, description? }>
156+
// (hierarchical format). We also keep a flat WellKnownCapabilities for backward compat.
157+
const wellKnown: WellKnownCapabilities = {
156158
feed: registeredServices.has('feed'),
157159
comments: registeredServices.has('feed'),
158160
automation: registeredServices.has('automation'),
@@ -162,6 +164,12 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
162164
chunkedUpload: registeredServices.has('file-storage'),
163165
};
164166

167+
// Convert flat booleans → hierarchical capability objects
168+
const capabilities: Record<string, { enabled: boolean; description?: string }> = {};
169+
for (const [key, enabled] of Object.entries(wellKnown)) {
170+
capabilities[key] = { enabled };
171+
}
172+
165173
return {
166174
version: '1.0',
167175
apiName: 'ObjectStack API',

0 commit comments

Comments
 (0)