@agent-os-lab/agent-game-sdk 0.1.2 → 0.1.4

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
@@ -2,13 +2,15 @@
2
2
 
3
3
  Embeddable agent game views and runtime clients.
4
4
 
5
+ For the full integration guide, see [USAGE.md](./USAGE.md).
6
+
5
7
  ## Office View
6
8
 
7
9
  Framework-neutral mount API:
8
10
 
9
11
  ```ts
10
- import { AgentGameRuntimeBrowserClient } from "agent-game-sdk";
11
- import { mountAgentGameOffice } from "agent-game-sdk/office";
12
+ import { AgentGameRuntimeBrowserClient } from "@agent-os-lab/agent-game-sdk";
13
+ import { mountAgentGameOffice } from "@agent-os-lab/agent-game-sdk/office";
12
14
 
13
15
  const client = new AgentGameRuntimeBrowserClient({
14
16
  baseUrl: "",
@@ -18,6 +20,13 @@ const client = new AgentGameRuntimeBrowserClient({
18
20
 
19
21
  const view = await mountAgentGameOffice(container, {
20
22
  renderer: "three",
23
+ office: {
24
+ rooms: [
25
+ { type: "office" },
26
+ { type: "auditorium", capacity: 40 },
27
+ { type: "gym" },
28
+ ],
29
+ },
21
30
  source: {
22
31
  type: "runtime",
23
32
  client,
@@ -25,36 +34,57 @@ const view = await mountAgentGameOffice(container, {
25
34
  });
26
35
 
27
36
  view.focusAgent("agent-1");
37
+ view.refreshAgents();
28
38
  view.destroy();
29
39
  ```
30
40
 
31
41
  React wrapper:
32
42
 
33
43
  ```tsx
34
- import { AgentGameRuntimeBrowserClient } from "agent-game-sdk";
35
- import { AgentGameOfficeView } from "agent-game-sdk/office/react";
44
+ import { useState } from "react";
45
+ import { AgentGameRuntimeBrowserClient } from "@agent-os-lab/agent-game-sdk";
46
+ import type { AgentGameOfficeController } from "@agent-os-lab/agent-game-sdk/office";
47
+ import { AgentGameOfficeView } from "@agent-os-lab/agent-game-sdk/office/react";
36
48
 
37
49
  const client = new AgentGameRuntimeBrowserClient({
38
50
  baseUrl: "",
39
51
  });
40
52
 
41
53
  export function Office() {
54
+ const [view, setView] = useState<AgentGameOfficeController | null>(null);
55
+
42
56
  return (
43
- <AgentGameOfficeView
44
- renderer="three"
45
- source={{ type: "runtime", client }}
46
- style={{ height: 640 }}
47
- />
57
+ <>
58
+ <button type="button" onClick={() => view?.refreshAgents()}>
59
+ Refresh agents
60
+ </button>
61
+ <AgentGameOfficeView
62
+ renderer="three"
63
+ office={{
64
+ rooms: [
65
+ { type: "office" },
66
+ { type: "auditorium" },
67
+ { type: "gym" },
68
+ ],
69
+ }}
70
+ source={{ type: "runtime", client }}
71
+ style={{ height: 640 }}
72
+ onReady={setView}
73
+ onDestroy={() => setView(null)}
74
+ />
75
+ </>
48
76
  );
49
77
  }
50
78
  ```
51
79
 
52
80
  `agent-game-sdk/office` is renderer and framework neutral. `agent-game-sdk/office/react` is the React-only entrypoint.
53
81
 
82
+ The built-in 3D renderer supports semantic room configuration. If `office` is omitted, the SDK renders the default `office + auditorium + gym` layout. Consumers choose room types and optional capacity; component coordinates and Three.js objects stay internal to the SDK.
83
+
54
84
  Browser clients should call an application BFF endpoint. Keep the AgentOS service API key on the server; the SDK server client forwards it to Agent Game Runtime:
55
85
 
56
86
  ```ts
57
- import { AgentGameRuntimeServerClient } from "agent-game-sdk";
87
+ import { AgentGameRuntimeServerClient } from "@agent-os-lab/agent-game-sdk";
58
88
 
59
89
  const client = new AgentGameRuntimeServerClient({
60
90
  baseUrl: process.env.AGENT_GAME_RUNTIME_BASE_URL!,
package/USAGE.md ADDED
@@ -0,0 +1,333 @@
1
+ # Agent Game SDK Usage Guide
2
+
3
+ This document describes how to use `@agent-os-lab/agent-game-sdk` from browser-facing applications and trusted backend services.
4
+
5
+ ## Overview
6
+
7
+ `@agent-os-lab/agent-game-sdk` provides embeddable agent game views and a browser-safe client for Agent Game Runtime presence streams.
8
+
9
+ Use it when another application needs to render an AgentOS office view, subscribe to live agent presence, or bridge a trusted backend API key into short-lived runtime tokens for browser clients.
10
+
11
+ The SDK is intentionally split by responsibility:
12
+
13
+ - Runtime clients handle token creation and WebSocket subscription.
14
+ - Office view APIs mount a framework-neutral 3D office view into a DOM container.
15
+ - React APIs wrap the same office view for React applications.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @agent-os-lab/agent-game-sdk
21
+ ```
22
+
23
+ Runtime requirements:
24
+
25
+ - A modern browser runtime with `fetch`, `Headers`, `Response`, and `WebSocket` for frontend usage.
26
+ - A trusted backend runtime with `fetch` for runtime token proxy endpoints.
27
+ - React 19+ and React DOM 19+ only when using `@agent-os-lab/agent-game-sdk/office/react`.
28
+
29
+ ## Entry Points
30
+
31
+ ```ts
32
+ import {
33
+ AgentGameRuntimeBrowserClient,
34
+ AgentGameRuntimeServerClient,
35
+ } from "@agent-os-lab/agent-game-sdk";
36
+ import { mountAgentGameOffice } from "@agent-os-lab/agent-game-sdk/office";
37
+ import { AgentGameOfficeView } from "@agent-os-lab/agent-game-sdk/office/react";
38
+ import type { AgentPresence } from "@agent-os-lab/agent-game-sdk/office";
39
+ ```
40
+
41
+ Available entry points:
42
+
43
+ - `@agent-os-lab/agent-game-sdk`: runtime clients and office exports.
44
+ - `@agent-os-lab/agent-game-sdk/office`: framework-neutral office view APIs and office types.
45
+ - `@agent-os-lab/agent-game-sdk/office/react`: React office view component.
46
+
47
+ Do not import files from `src/` in application code. Use the published entry points above.
48
+
49
+ ## Authentication Model
50
+
51
+ Use `AgentGameRuntimeServerClient` only in trusted backend code. It sends the AgentOS service API key as a bearer token to Agent Game Runtime and returns a short-lived runtime token response.
52
+
53
+ ```ts
54
+ import { AgentGameRuntimeServerClient } from "@agent-os-lab/agent-game-sdk";
55
+
56
+ const serverClient = new AgentGameRuntimeServerClient({
57
+ baseUrl: process.env.AGENT_GAME_RUNTIME_BASE_URL!,
58
+ apiKey: process.env.AGENTOS_API_KEY!,
59
+ requestId: () => crypto.randomUUID(),
60
+ });
61
+
62
+ export async function GET() {
63
+ return Response.json(await serverClient.createRuntimeToken());
64
+ }
65
+ ```
66
+
67
+ Do not expose `AGENTOS_API_KEY` or any AgentOS service API key to browser code.
68
+
69
+ Use `AgentGameRuntimeBrowserClient` in the frontend against a same-origin BFF/proxy endpoint that returns `GameRuntimeTokenResponse`.
70
+
71
+ ```ts
72
+ import { AgentGameRuntimeBrowserClient } from "@agent-os-lab/agent-game-sdk";
73
+
74
+ const browserClient = new AgentGameRuntimeBrowserClient({
75
+ baseUrl: "",
76
+ tokenPath: "/api/agent-game-runtime-token",
77
+ });
78
+ ```
79
+
80
+ If your application backend expects an application-scoped browser token, provide `accessToken`. This token is for your BFF/proxy, not for Agent Game Runtime directly.
81
+
82
+ ```ts
83
+ const browserClient = new AgentGameRuntimeBrowserClient({
84
+ baseUrl: "",
85
+ tokenPath: "/api/agent-game-runtime-token",
86
+ accessToken: async () => getScopedApplicationToken(),
87
+ });
88
+ ```
89
+
90
+ The browser client strips caller-provided `authorization`, `x-hermes-tenant-id`, and `x-agentos-tenant-id` headers. Only the `accessToken` option may set a browser bearer token.
91
+
92
+ ## Runtime Subscription
93
+
94
+ Use `subscribe` when the application wants direct access to live runtime messages instead of using the office view.
95
+
96
+ ```ts
97
+ const subscription = await browserClient.subscribe({
98
+ onSnapshot: (message) => {
99
+ console.log("snapshot", message.agents);
100
+ },
101
+ onPatch: (message) => {
102
+ console.log("patch", message.agents);
103
+ },
104
+ onError: (error) => {
105
+ console.error("runtime stream error", error);
106
+ },
107
+ });
108
+
109
+ subscription.close();
110
+ ```
111
+
112
+ Runtime messages are either:
113
+
114
+ - `snapshot`: full current presence list for the tenant.
115
+ - `patch`: changed agent presence records.
116
+
117
+ Use `mergeAgentPresence` when maintaining your own local presence array from snapshots and patches.
118
+
119
+ ```ts
120
+ import { mergeAgentPresence } from "@agent-os-lab/agent-game-sdk";
121
+
122
+ let agents = [];
123
+
124
+ await browserClient.subscribe({
125
+ onSnapshot: (message) => {
126
+ agents = message.agents;
127
+ },
128
+ onPatch: (message) => {
129
+ agents = mergeAgentPresence(agents, message.agents);
130
+ },
131
+ });
132
+ ```
133
+
134
+ ## Office View
135
+
136
+ Use `mountAgentGameOffice` for framework-neutral embedding.
137
+
138
+ ```ts
139
+ import { AgentGameRuntimeBrowserClient } from "@agent-os-lab/agent-game-sdk";
140
+ import { mountAgentGameOffice } from "@agent-os-lab/agent-game-sdk/office";
141
+
142
+ const client = new AgentGameRuntimeBrowserClient({
143
+ baseUrl: "",
144
+ tokenPath: "/api/agent-game-runtime-token",
145
+ });
146
+
147
+ const view = await mountAgentGameOffice(container, {
148
+ renderer: "three",
149
+ office: {
150
+ rooms: [
151
+ { type: "office", capacity: 12 },
152
+ { type: "auditorium", capacity: 40 },
153
+ { type: "gym", capacity: 8 },
154
+ ],
155
+ },
156
+ source: {
157
+ type: "runtime",
158
+ client,
159
+ },
160
+ });
161
+
162
+ view.focusAgent("agent-1");
163
+ view.refreshAgents();
164
+ view.destroy();
165
+ ```
166
+
167
+ `renderer: "three"` is the built-in renderer and the default renderer. The container must have a stable size; the React wrapper supplies a default `minHeight`, but framework-neutral callers should size the container in CSS.
168
+
169
+ `office.rooms` configures the 3D office by semantic room type. The built-in room types are `office`, `auditorium`, and `gym`. If `office` is omitted, the SDK renders all three rooms. `capacity` is optional and influences the generated anchors used for agent placement. Do not configure desks, walls, whiteboards, fitness equipment, or raw coordinates from application code; those components remain SDK-managed presets.
170
+
171
+ `refreshAgents` asks Agent Game Runtime to refresh the tenant roster through the existing bootstrap flow. It is intended for UI controls such as a refresh button after an Agent was created or deleted in another surface.
172
+
173
+ ## React Office View
174
+
175
+ Use `AgentGameOfficeView` when embedding the office view in a React application.
176
+
177
+ ```tsx
178
+ import { useState } from "react";
179
+ import { AgentGameRuntimeBrowserClient } from "@agent-os-lab/agent-game-sdk";
180
+ import type { AgentGameOfficeController } from "@agent-os-lab/agent-game-sdk/office";
181
+ import { AgentGameOfficeView } from "@agent-os-lab/agent-game-sdk/office/react";
182
+
183
+ const client = new AgentGameRuntimeBrowserClient({
184
+ baseUrl: "",
185
+ });
186
+
187
+ export function Office() {
188
+ const [view, setView] = useState<AgentGameOfficeController | null>(null);
189
+
190
+ return (
191
+ <>
192
+ <button type="button" onClick={() => view?.refreshAgents()}>
193
+ Refresh agents
194
+ </button>
195
+ <AgentGameOfficeView
196
+ renderer="three"
197
+ office={{
198
+ rooms: [
199
+ { type: "office" },
200
+ { type: "auditorium" },
201
+ { type: "gym" },
202
+ ],
203
+ }}
204
+ source={{ type: "runtime", client }}
205
+ focusedAgentId="agent-1"
206
+ style={{ height: 640 }}
207
+ onReady={setView}
208
+ onDestroy={() => setView(null)}
209
+ />
210
+ </>
211
+ );
212
+ }
213
+ ```
214
+
215
+ Keep `source` object identity stable across renders when possible. Recreating `source` every render can remount the view because the React wrapper treats it as an effect dependency. Equivalent inline `office` objects do not remount the view, but memoizing office config is still preferable when constructing it dynamically.
216
+
217
+ ## Snapshot Source
218
+
219
+ Use `source.type: "snapshot"` for demos, tests, static previews, or applications that already have agent presence data.
220
+
221
+ ```ts
222
+ import { mountAgentGameOffice, type AgentPresence } from "@agent-os-lab/agent-game-sdk/office";
223
+
224
+ const agents: AgentPresence[] = [
225
+ {
226
+ tenantId: "tenant-demo",
227
+ agentId: "agent-1",
228
+ displayName: "Research Agent",
229
+ status: "working",
230
+ statusSource: "simulation",
231
+ activity: {
232
+ kind: "running",
233
+ summary: "Reading customer notes",
234
+ updatedAt: new Date().toISOString(),
235
+ },
236
+ updatedAt: new Date().toISOString(),
237
+ },
238
+ ];
239
+
240
+ const view = await mountAgentGameOffice(container, {
241
+ source: {
242
+ type: "snapshot",
243
+ agents,
244
+ },
245
+ });
246
+
247
+ view.updateAgents([
248
+ {
249
+ ...agents[0],
250
+ status: "meeting",
251
+ updatedAt: new Date().toISOString(),
252
+ },
253
+ ]);
254
+ ```
255
+
256
+ `updateAgents` only mutates SDK-managed snapshot sources. Runtime and custom sources own their own updates.
257
+
258
+ ## Custom Source
259
+
260
+ Use `source.type: "custom"` when another state manager already projects runtime state into office snapshots.
261
+
262
+ ```ts
263
+ import type {
264
+ AgentGameOfficeSnapshot,
265
+ AgentGameOfficeSource,
266
+ } from "@agent-os-lab/agent-game-sdk/office";
267
+
268
+ const source: AgentGameOfficeSource = {
269
+ subscribe(listener: (snapshot: AgentGameOfficeSnapshot) => void) {
270
+ const unsubscribe = store.subscribe(() => {
271
+ listener(store.getState().officeSnapshot);
272
+ });
273
+ listener(store.getState().officeSnapshot);
274
+ return unsubscribe;
275
+ },
276
+ };
277
+
278
+ await mountAgentGameOffice(container, {
279
+ source: {
280
+ type: "custom",
281
+ source,
282
+ },
283
+ });
284
+ ```
285
+
286
+ ## Agent Presence Shape
287
+
288
+ Office rendering is driven by `AgentPresence`.
289
+
290
+ Important fields:
291
+
292
+ - `tenantId`: tenant that owns the presence record.
293
+ - `agentId`: stable agent identifier.
294
+ - `displayName`: label shown in the office view.
295
+ - `status`: one of `working`, `thinking`, `meeting`, `resting`, `entertaining`, `idle`, or `offline`.
296
+ - `statusSource`: `runtime`, `simulation`, or `system`.
297
+ - `activity`: optional visible activity summary and preview.
298
+ - `updatedAt`: ISO timestamp for the latest presence update.
299
+ - `expiresAt`: optional ISO timestamp after which the presence should be considered stale by the producer.
300
+
301
+ ## Local Demo
302
+
303
+ Run the SDK office view demo from `agent-game-sdk`:
304
+
305
+ ```bash
306
+ AGENT_GAME_RUNTIME_BASE_URL=http://localhost:3107 AGENTOS_API_KEY=... bun run demo
307
+ ```
308
+
309
+ Open `http://localhost:7357`.
310
+
311
+ Set `PORT=7358` or another value to change the port.
312
+
313
+ ## Publish
314
+
315
+ Publishing is controlled from the SDK package directory. The script bumps the package version, builds the package, runs `npm pack --dry-run`, and then publishes to npm:
316
+
317
+ ```bash
318
+ bun run sdk:publish
319
+ ```
320
+
321
+ The default release is patch. Use `--minor` or `--major` when needed:
322
+
323
+ ```bash
324
+ bun run sdk:publish -- --minor
325
+ ```
326
+
327
+ Use `--dry-run` to validate the next version and package contents without publishing. The script restores the previous version after a dry run.
328
+
329
+ If npm requires two-factor authentication, pass the one-time password with `--otp`:
330
+
331
+ ```bash
332
+ bun run sdk:publish -- --otp 123456
333
+ ```
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@agent-os-lab/agent-game-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
7
- "README.md"
7
+ "README.md",
8
+ "USAGE.md"
8
9
  ],
9
10
  "exports": {
10
11
  ".": "./src/index.ts",
@@ -5,6 +5,10 @@ import type {
5
5
  AgentPresenceStatus,
6
6
  GameRuntimeSubscription,
7
7
  } from "../../runtime-client";
8
+ import type {
9
+ AgentGameOfficeConfig,
10
+ ResolvedOfficeLayout,
11
+ } from "../layout";
8
12
 
9
13
  export type {
10
14
  AgentActivityDisplay,
@@ -95,16 +99,19 @@ export type AgentGameOfficeRenderer = {
95
99
  export type AgentGameOfficeMountOptions = {
96
100
  renderer?: AgentGameOfficeRendererKind | AgentGameOfficeRenderer;
97
101
  source: AgentGameOfficeSourceInput;
102
+ office?: AgentGameOfficeConfig;
98
103
  focusedAgentId?: string | null;
99
104
  className?: string;
100
105
  };
101
106
 
102
107
  export type AgentGameOfficeResolvedOptions = Omit<AgentGameOfficeMountOptions, "renderer"> & {
103
108
  renderer: AgentGameOfficeRendererKind | AgentGameOfficeRenderer;
109
+ officeLayout: ResolvedOfficeLayout;
104
110
  };
105
111
 
106
112
  export type AgentGameOfficeController = {
107
113
  updateAgents(agents: AgentPresence[]): void;
108
114
  focusAgent(agentId: string | null): void;
115
+ refreshAgents(): void;
109
116
  destroy(): void;
110
117
  };
@@ -1,4 +1,5 @@
1
1
  export * from "./core/types";
2
2
  export * from "./core/projection";
3
3
  export * from "./core/source";
4
+ export * from "./layout";
4
5
  export * from "./mount";
@@ -0,0 +1,76 @@
1
+ export type AgentGameOfficeRoomType = "office" | "auditorium" | "gym";
2
+
3
+ export type AgentGameOfficeRoomConfig = {
4
+ type: AgentGameOfficeRoomType;
5
+ capacity?: number;
6
+ };
7
+
8
+ export type AgentGameOfficeConfig = {
9
+ rooms: AgentGameOfficeRoomConfig[];
10
+ };
11
+
12
+ export type NormalizedOfficeRoomConfig = {
13
+ id: string;
14
+ type: AgentGameOfficeRoomType;
15
+ capacity: number;
16
+ };
17
+
18
+ export type NormalizedOfficeConfig = {
19
+ rooms: NormalizedOfficeRoomConfig[];
20
+ };
21
+
22
+ const DEFAULT_ROOM_CAPACITY = {
23
+ office: 12,
24
+ auditorium: 40,
25
+ gym: 8,
26
+ } satisfies Record<AgentGameOfficeRoomType, number>;
27
+
28
+ const MAX_ROOM_CAPACITY = {
29
+ office: 48,
30
+ auditorium: 120,
31
+ gym: 24,
32
+ } satisfies Record<AgentGameOfficeRoomType, number>;
33
+
34
+ export const DEFAULT_OFFICE_CONFIG: AgentGameOfficeConfig = {
35
+ rooms: [
36
+ { type: "office" },
37
+ { type: "auditorium" },
38
+ { type: "gym" },
39
+ ],
40
+ };
41
+
42
+ export function normalizeOfficeConfig(
43
+ config: AgentGameOfficeConfig | undefined = DEFAULT_OFFICE_CONFIG,
44
+ ): NormalizedOfficeConfig {
45
+ if (!Array.isArray(config.rooms) || config.rooms.length === 0) {
46
+ throw new Error("Agent game office config requires at least one room");
47
+ }
48
+
49
+ const counts = new Map<AgentGameOfficeRoomType, number>();
50
+ return {
51
+ rooms: config.rooms.map((room) => {
52
+ assertRoomType(room.type);
53
+ const nextCount = (counts.get(room.type) ?? 0) + 1;
54
+ counts.set(room.type, nextCount);
55
+ return {
56
+ id: `${room.type}-${nextCount}`,
57
+ type: room.type,
58
+ capacity: normalizeRoomCapacity(room.type, room.capacity),
59
+ };
60
+ }),
61
+ };
62
+ }
63
+
64
+ function assertRoomType(value: string): asserts value is AgentGameOfficeRoomType {
65
+ if (value !== "office" && value !== "auditorium" && value !== "gym") {
66
+ throw new Error(`Unsupported agent game office room type: ${value}`);
67
+ }
68
+ }
69
+
70
+ function normalizeRoomCapacity(type: AgentGameOfficeRoomType, capacity: number | undefined): number {
71
+ const value = capacity ?? DEFAULT_ROOM_CAPACITY[type];
72
+ if (!Number.isInteger(value) || value <= 0) {
73
+ throw new Error(`Agent game office room ${type} capacity must be a positive integer`);
74
+ }
75
+ return Math.min(value, MAX_ROOM_CAPACITY[type]);
76
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./config";
2
+ export * from "./resolver";