@openwop/openwop 1.3.0 → 1.5.0

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/src/client.ts CHANGED
@@ -12,6 +12,13 @@ import { streamEvents, type EventsStreamOptions } from './sse.js';
12
12
  import {
13
13
  WopError,
14
14
  type AuditVerifyResult,
15
+ type CreateTriggerSubscriptionResponse,
16
+ type LocalizedContentLanguageSettings,
17
+ type LocalizedContentPage,
18
+ type LocalizedContentPageResponse,
19
+ type LocalizedContentSection,
20
+ type PutContentSectionRequest,
21
+ type TriggerSubscriptionRegistration,
15
22
  type BulkCancelRunsRequest,
16
23
  type BulkCancelRunsResponse,
17
24
  type Capabilities,
@@ -57,6 +64,7 @@ import {
57
64
  type OrgChartResponsibilityView,
58
65
  type EvalSummary,
59
66
  type ToolDescriptor,
67
+ type CompactToolDescriptor,
60
68
  type AgentDeployment,
61
69
  type AgentDeploymentTransition,
62
70
  type CreateUserAgentRequest,
@@ -529,15 +537,40 @@ export class OpenwopClient {
529
537
  }
530
538
  },
531
539
 
540
+ /**
541
+ * RFC 0112 — list the `CompactToolDescriptor`s via `GET /v1/tools?view=compact`
542
+ * when the host advertises `capabilities.toolCatalog.compactView`. Unwraps the
543
+ * `{ tools: CompactToolDescriptor[] }` envelope. Returns `null` when the host
544
+ * doesn't advertise the catalog (the endpoint 404s).
545
+ */
546
+ listCompact: async (): Promise<readonly CompactToolDescriptor[] | null> => {
547
+ try {
548
+ const res = await this.#request<{ tools?: readonly CompactToolDescriptor[] }>({
549
+ method: 'GET',
550
+ path: '/v1/tools?view=compact',
551
+ });
552
+ return res.tools ?? [];
553
+ } catch (err) {
554
+ if (err instanceof WopError && err.status === 404) return null;
555
+ throw err;
556
+ }
557
+ },
558
+
532
559
  /**
533
560
  * RFC 0078 §B — return one `ToolDescriptor` by its stable `toolId`. Returns
534
- * `null` on 404 (no such tool, or the capability is unadvertised).
561
+ * `null` on 404 (no such tool, or the capability is unadvertised). Pass
562
+ * `{ view: 'compact' }` (RFC 0112) to receive the `CompactToolDescriptor`
563
+ * projection instead.
535
564
  */
536
- get: async (toolId: string): Promise<ToolDescriptor | null> => {
565
+ get: async (
566
+ toolId: string,
567
+ opts: { readonly view?: 'standard' | 'compact' } = {},
568
+ ): Promise<ToolDescriptor | CompactToolDescriptor | null> => {
569
+ const query = opts.view === 'compact' ? '?view=compact' : '';
537
570
  try {
538
- return await this.#request<ToolDescriptor>({
571
+ return await this.#request<ToolDescriptor | CompactToolDescriptor>({
539
572
  method: 'GET',
540
- path: `/v1/tools/${encodeURIComponent(toolId)}`,
573
+ path: `/v1/tools/${encodeURIComponent(toolId)}${query}`,
541
574
  });
542
575
  } catch (err) {
543
576
  if (err instanceof WopError && err.status === 404) return null;
@@ -837,6 +870,108 @@ export class OpenwopClient {
837
870
  },
838
871
  };
839
872
 
873
+ // ── RFC 0103 Localized content surface (gated on capabilities.content) ──
874
+ readonly content = {
875
+ /** `GET /v1/content/pages` — list page records. Returns `null` when the
876
+ * host doesn't advertise `capabilities.content` (501). */
877
+ listPages: async (): Promise<readonly LocalizedContentPage[] | null> => {
878
+ try {
879
+ return await this.#request<readonly LocalizedContentPage[]>({
880
+ method: 'GET',
881
+ path: '/v1/content/pages',
882
+ });
883
+ } catch (err) {
884
+ if (err instanceof WopError && err.status === 501) return null;
885
+ throw err;
886
+ }
887
+ },
888
+
889
+ /** `GET /v1/content/pages/{slug}` — the negotiated locale's resolved page +
890
+ * sections. `acceptLanguage` rides the `Accept-Language` header (the Stable
891
+ * `i18n.md` negotiation; no `?locale=`). Returns `null` on `404`
892
+ * (no such published page) or `501` (uncapable). */
893
+ getPage: async (
894
+ slug: string,
895
+ acceptLanguage?: string,
896
+ ): Promise<LocalizedContentPageResponse | null> => {
897
+ try {
898
+ return await this.#request<LocalizedContentPageResponse>({
899
+ method: 'GET',
900
+ path: `/v1/content/pages/${encodeURIComponent(slug)}`,
901
+ ...(acceptLanguage
902
+ ? { headers: { 'Accept-Language': acceptLanguage } }
903
+ : {}),
904
+ });
905
+ } catch (err) {
906
+ if (err instanceof WopError && (err.status === 404 || err.status === 501))
907
+ return null;
908
+ throw err;
909
+ }
910
+ },
911
+
912
+ /** `POST /v1/content/pages` — create a page record (admin). Throws the
913
+ * typed `WopError` on `400`/`401`/`403`. */
914
+ createPage: (body: LocalizedContentPage): Promise<LocalizedContentPage> =>
915
+ this.#request<LocalizedContentPage>({
916
+ method: 'POST',
917
+ path: '/v1/content/pages',
918
+ body,
919
+ }),
920
+
921
+ /** `PUT /v1/content/pages/{pageId}/sections/{sectionId}` — upsert a
922
+ * section's field overlay for a locale (admin). */
923
+ putSection: (
924
+ pageId: string,
925
+ sectionId: string,
926
+ body: PutContentSectionRequest,
927
+ ): Promise<LocalizedContentSection> =>
928
+ this.#request<LocalizedContentSection>({
929
+ method: 'PUT',
930
+ path: `/v1/content/pages/${encodeURIComponent(pageId)}/sections/${encodeURIComponent(sectionId)}`,
931
+ body,
932
+ }),
933
+
934
+ /** `GET /v1/content/settings` — language settings. Returns `null` when the
935
+ * host doesn't advertise `capabilities.content` (501). */
936
+ getSettings: async (): Promise<LocalizedContentLanguageSettings | null> => {
937
+ try {
938
+ return await this.#request<LocalizedContentLanguageSettings>({
939
+ method: 'GET',
940
+ path: '/v1/content/settings',
941
+ });
942
+ } catch (err) {
943
+ if (err instanceof WopError && err.status === 501) return null;
944
+ throw err;
945
+ }
946
+ },
947
+
948
+ /** `PUT /v1/content/settings` — replace language settings (admin). */
949
+ putSettings: (
950
+ body: LocalizedContentLanguageSettings,
951
+ ): Promise<LocalizedContentLanguageSettings> =>
952
+ this.#request<LocalizedContentLanguageSettings>({
953
+ method: 'PUT',
954
+ path: '/v1/content/settings',
955
+ body,
956
+ }),
957
+ };
958
+
959
+ // ── RFC 0099 Trigger subscriptions (gated on capabilities.triggerBridge) ──
960
+ readonly triggerSubscriptions = {
961
+ /** `POST /v1/trigger-subscriptions` — register an external-event trigger.
962
+ * The `binding.secret*` is returned ONCE at creation (SR-1); persist it.
963
+ * Throws the typed `WopError` on `400`/`401`/`403`, or `501` when the host
964
+ * doesn't advertise the trigger-bridge ingestion surface. */
965
+ create: (
966
+ body: TriggerSubscriptionRegistration,
967
+ ): Promise<CreateTriggerSubscriptionResponse> =>
968
+ this.#request<CreateTriggerSubscriptionResponse>({
969
+ method: 'POST',
970
+ path: '/v1/trigger-subscriptions',
971
+ body,
972
+ }),
973
+ };
974
+
840
975
  // ── Agent workspace files (RFC 0059; gated on capabilities.workspace) ──
841
976
  readonly workspace = {
842
977
  /**
@@ -39,6 +39,14 @@ import type {
39
39
  OutputChunkPayload,
40
40
  RunEventDoc,
41
41
  TypedRunEvent,
42
+ VoiceSpeechStartPayload,
43
+ VoiceTranscriptPayload,
44
+ VoiceEndpointCandidatePayload,
45
+ VoiceTurnCommitPayload,
46
+ VoiceSynthesisChunkPayload,
47
+ VoiceBargeInPayload,
48
+ VoiceCancelledPayload,
49
+ ChannelPresencePayload,
42
50
  } from './types.js';
43
51
 
44
52
  // ─── Type guards ────────────────────────────────────────────────────────
@@ -160,6 +168,97 @@ export function isOutputChunk(
160
168
  return typeof (ev.payload as Record<string, unknown>).isLast === 'boolean';
161
169
  }
162
170
 
171
+ function hasNumberField(payload: unknown, field: string): boolean {
172
+ return (
173
+ payload !== null &&
174
+ typeof payload === 'object' &&
175
+ typeof (payload as Record<string, unknown>)[field] === 'number'
176
+ );
177
+ }
178
+
179
+ /** `voice.speech_start` (RFC 0106). */
180
+ export function isVoiceSpeechStart(
181
+ ev: RunEventDoc,
182
+ ): ev is TypedRunEvent<VoiceSpeechStartPayload> {
183
+ return ev.type === 'voice.speech_start' && hasNumberField(ev.payload, 'atMs');
184
+ }
185
+
186
+ /** `voice.transcript` (RFC 0106). Narrows when `type` matches AND payload
187
+ * carries the required `text` + `isFinal` + `atMs` + `contentTrust`. The
188
+ * transcript is untrusted ingress (`voice-transcript-untrusted`). */
189
+ export function isVoiceTranscript(
190
+ ev: RunEventDoc,
191
+ ): ev is TypedRunEvent<VoiceTranscriptPayload> {
192
+ return (
193
+ ev.type === 'voice.transcript' &&
194
+ hasStringField(ev.payload, 'text') &&
195
+ typeof (ev.payload as Record<string, unknown>).isFinal === 'boolean' &&
196
+ hasNumberField(ev.payload, 'atMs') &&
197
+ (ev.payload as Record<string, unknown>).contentTrust === 'untrusted'
198
+ );
199
+ }
200
+
201
+ /** `voice.endpoint_candidate` (RFC 0106). */
202
+ export function isVoiceEndpointCandidate(
203
+ ev: RunEventDoc,
204
+ ): ev is TypedRunEvent<VoiceEndpointCandidatePayload> {
205
+ return (
206
+ ev.type === 'voice.endpoint_candidate' && hasNumberField(ev.payload, 'atMs')
207
+ );
208
+ }
209
+
210
+ /** `voice.turn_commit` (RFC 0106). Narrows when payload carries the required
211
+ * `atMs` + `finalText` (the settled transcript). */
212
+ export function isVoiceTurnCommit(
213
+ ev: RunEventDoc,
214
+ ): ev is TypedRunEvent<VoiceTurnCommitPayload> {
215
+ return (
216
+ ev.type === 'voice.turn_commit' &&
217
+ hasNumberField(ev.payload, 'atMs') &&
218
+ hasStringField(ev.payload, 'finalText')
219
+ );
220
+ }
221
+
222
+ /** `voice.synthesis_chunk` (RFC 0106). Narrows when payload carries the
223
+ * required `seq` (number) + `mimeType` (string). */
224
+ export function isVoiceSynthesisChunk(
225
+ ev: RunEventDoc,
226
+ ): ev is TypedRunEvent<VoiceSynthesisChunkPayload> {
227
+ return (
228
+ ev.type === 'voice.synthesis_chunk' &&
229
+ hasNumberField(ev.payload, 'seq') &&
230
+ hasStringField(ev.payload, 'mimeType')
231
+ );
232
+ }
233
+
234
+ /** `voice.barge_in` (RFC 0106). */
235
+ export function isVoiceBargeIn(
236
+ ev: RunEventDoc,
237
+ ): ev is TypedRunEvent<VoiceBargeInPayload> {
238
+ return ev.type === 'voice.barge_in' && hasNumberField(ev.payload, 'atMs');
239
+ }
240
+
241
+ /** `voice.cancelled` (RFC 0106). */
242
+ export function isVoiceCancelled(
243
+ ev: RunEventDoc,
244
+ ): ev is TypedRunEvent<VoiceCancelledPayload> {
245
+ return ev.type === 'voice.cancelled' && hasNumberField(ev.payload, 'atMs');
246
+ }
247
+
248
+ /** `channel.presence` (RFC 0110). EPHEMERAL — observable on the LIVE event
249
+ * stream only; ABSENT on replay / `:fork` (the host never persists presence
250
+ * to the replayable log). Narrows when payload carries `conversationId` +
251
+ * a `present` array. */
252
+ export function isChannelPresence(
253
+ ev: RunEventDoc,
254
+ ): ev is TypedRunEvent<ChannelPresencePayload> {
255
+ return (
256
+ ev.type === 'channel.presence' &&
257
+ hasStringField(ev.payload, 'conversationId') &&
258
+ Array.isArray((ev.payload as Record<string, unknown>).present)
259
+ );
260
+ }
261
+
163
262
  // ─── High-level subscription helper ─────────────────────────────────────
164
263
 
165
264
  /** Returned by {@link subscribeToAgentReasoning}. Call to cancel the
package/src/index.ts CHANGED
@@ -61,6 +61,16 @@ export type {
61
61
  MemoryWrittenPayload,
62
62
  // RFC 0094 §D — streaming output chunk (`output.chunk` / `ai.message.chunk`)
63
63
  OutputChunkPayload,
64
+ // RFC 0106 — voice.* run-event payloads
65
+ VoiceSpeechStartPayload,
66
+ VoiceTranscriptPayload,
67
+ VoiceEndpointCandidatePayload,
68
+ VoiceTurnCommitPayload,
69
+ VoiceSynthesisChunkPayload,
70
+ VoiceBargeInPayload,
71
+ VoiceCancelledPayload,
72
+ // RFC 0110 — channel.presence run-event payload
73
+ ChannelPresencePayload,
64
74
  // RFC 0027 + RFC 0028 — Prompt library (spec/v1/prompts.md)
65
75
  GetPromptRequest,
66
76
  ListPromptsRequest,
@@ -116,6 +126,14 @@ export {
116
126
  isAgentDecided,
117
127
  isMemoryWritten,
118
128
  isOutputChunk,
129
+ isVoiceSpeechStart,
130
+ isVoiceTranscript,
131
+ isVoiceEndpointCandidate,
132
+ isVoiceTurnCommit,
133
+ isVoiceSynthesisChunk,
134
+ isVoiceBargeIn,
135
+ isVoiceCancelled,
136
+ isChannelPresence,
119
137
  subscribeToAgentReasoning,
120
138
  } from './event-helpers.js';
121
139
  export type {
@@ -183,6 +201,9 @@ export type {
183
201
  // AI Envelope types (DRAFT v1.x — spec/v1/ai-envelope.md). Inbound LLM-emission
184
202
  // envelope, distinct from RunEventDoc (outbound) and ErrorEnvelope (host HTTP).
185
203
  export type {
204
+ A2UISurfacePayload,
205
+ A2uiSurfaceDeltaFrame,
206
+ A2uiSurfacePatchOp,
186
207
  AIEnvelope,
187
208
  AIEnvelopeErrorPayload,
188
209
  ClarificationRequestPayload,
@@ -196,6 +217,17 @@ export type {
196
217
  SchemaRequestPayload,
197
218
  SchemaResponsePayload,
198
219
  ValidationDetail,
220
+ // RFC 0103 — localized content surface
221
+ LocalizedContentStatus,
222
+ LocalizedContentPage,
223
+ LocalizedContentSection,
224
+ LocalizedContentPageResponse,
225
+ LocalizedContentLanguageSettings,
226
+ PutContentSectionRequest,
227
+ // RFC 0099 — trigger subscription registration
228
+ TriggerSubscriptionRegistration,
229
+ TriggerSubscription,
230
+ CreateTriggerSubscriptionResponse,
199
231
  } from './types.js';
200
232
 
201
233
  // RFC 0030 §A `reasoning` field prompt-directive helper. Hosts that