@happyvertical/smrt-mcp-openai 0.0.0-stage → 0.51.38
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/AGENTS.md +11 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +175 -2
- package/dist/client.d.ts +27 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +125 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +47 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +110 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -0
- package/dist/mentions.d.ts +30 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +148 -0
- package/dist/mentions.js.map +1 -0
- package/dist/messages.d.ts +19 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +47 -0
- package/dist/messages.js.map +1 -0
- package/dist/settings.d.ts +45 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +158 -0
- package/dist/settings.js.map +1 -0
- package/dist/validation.d.ts +12 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +95 -0
- package/dist/validation.js.map +1 -0
- package/package.json +65 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Optional OpenAI navigation adapter
|
|
2
|
+
|
|
3
|
+
`src/index.ts` composes existing principal-authorized app-MCP workflows. Settings
|
|
4
|
+
persistence, atomic assignment and tenant checks stay in the owning application
|
|
5
|
+
handler. The adapter validates wire contracts; it never authenticates or retries
|
|
6
|
+
writes. `src/client.ts` uses only the portable browser bridge and bounded inert
|
|
7
|
+
host context. No SDK v1 helper, second server, or OpenAI types in portable APIs.
|
|
8
|
+
|
|
9
|
+
Pinned contract, fallbacks, package gates and external-host evidence limitations
|
|
10
|
+
are in README.md; behavior/threat rows are in TEST-DESIGN.md. Run build, typecheck,
|
|
11
|
+
test, test:e2e and verify:pack. Synthetic hosts do not prove OpenAI host support.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,176 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @happyvertical/smrt-mcp-openai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Optional navigation, settings, context, messages and mentions for s-m-r-t MCP Apps. Ordinary s-m-r-t apps do not
|
|
4
|
+
import this package. The `./client` entry is browser-only and depends on the
|
|
5
|
+
portable `@happyvertical/smrt-mcp-apps` bridge; the root entry composes existing
|
|
6
|
+
app-MCP workflows without creating another server or authorization policy.
|
|
7
|
+
|
|
8
|
+
## Contract and scope
|
|
9
|
+
|
|
10
|
+
This package implements the navigation/settings and context/messages/mentions children of [#3202](https://github.com/happyvertical/smrt/issues/3202)
|
|
11
|
+
under the [M0 integration standard](../../docs/content/architecture/mcp-apps-integration.md):
|
|
12
|
+
|
|
13
|
+
- Global and thread entrypoint metadata on idempotent read UI tools accepting `{}`.
|
|
14
|
+
- Structured primitive settings and permission-filtered layout actions; modern
|
|
15
|
+
discovery advertises `capabilities.extensions['openai/settings']` only when
|
|
16
|
+
both settings tools are in the request's authorized catalog.
|
|
17
|
+
- Resource display-mode metadata and portable display requests with inline fallback.
|
|
18
|
+
- Validated native/web app links and observed host deep links resolved through an
|
|
19
|
+
existing domain-authorized read tool.
|
|
20
|
+
- Manifest onboarding declaration; packaged skill/assets verification belongs to M7.
|
|
21
|
+
|
|
22
|
+
Exact baseline: [OpenAI extension spec e314720](https://github.com/openai/mcp-extensions/blob/e314720a0daac326217d1f123fcf51647868fa9f/docs/spec.md),
|
|
23
|
+
revalidated as upstream main on 2026-09-30 UTC, and the [official extension guide](https://developers.openai.com/plugins/build/extensions)
|
|
24
|
+
and [UI guide](https://developers.openai.com/plugins/build/chatgpt-ui). The SDK v1
|
|
25
|
+
OpenAI helper is deliberately not a dependency. Server transport uses native
|
|
26
|
+
scoped MCP SDK 2.0.0 / protocol `2026-07-28`; browser Apps uses `2026-01-26`.
|
|
27
|
+
|
|
28
|
+
Only implemented features are declared. Files/forms remain separate planned M6 children. Settings support booleans, strings/enums/length bounds,
|
|
29
|
+
numbers/integers/range/multiple bounds. Arbitrary regex patterns are explicitly
|
|
30
|
+
rejected to avoid running unbounded patterns; applications can use an enum or
|
|
31
|
+
an existing domain validator. No silent schema downgrade occurs. Metadata is
|
|
32
|
+
bounded to 64 KiB, depth 16; settings to 64 fields, strings to 4096 characters,
|
|
33
|
+
routes to 2048 characters. Unknown fields/types fail closed.
|
|
34
|
+
|
|
35
|
+
## Server composition
|
|
36
|
+
|
|
37
|
+
Use `withOpenAiEntrypoints(existingReadWorkflow, ['global', 'thread'])` and pass
|
|
38
|
+
the result to the existing `createMcpAppServer({ workflowTools })`. It retains the
|
|
39
|
+
handler and portable `_meta.ui.resourceUri`. Initial tool results still render
|
|
40
|
+
without duplicate entrypoint calls. Give thread views distinct descriptive titles.
|
|
41
|
+
|
|
42
|
+
`bindOpenAiSettings({ schema, read, update, server: () => appServer })` wraps two
|
|
43
|
+
existing workflow definitions. Add its `workflows` to that server, then mount it
|
|
44
|
+
with `extensions: ({ tools }) => settings.extensions(tools)`. The callback runs
|
|
45
|
+
on `server/discover` with the same verified principal and filtered catalog used
|
|
46
|
+
for tool discovery. Neither annotations nor extension metadata grant permission.
|
|
47
|
+
|
|
48
|
+
Read handlers return `structuredContent: { schema, values, layout? }`, with a
|
|
49
|
+
value for every property. Update handlers receive `{ set: { changedField: value } }`
|
|
50
|
+
and return `structuredContent: { values }`. Updates are absolute-value assignments,
|
|
51
|
+
not toggles or increments. The binding verifies their declared read/write effects,
|
|
52
|
+
requires idempotent assignment, validates inputs/outputs, and never retries.
|
|
53
|
+
|
|
54
|
+
**Owning handlers must authorize resource ownership, recheck active tenant and
|
|
55
|
+
membership, and persist patches atomically using their existing executor and
|
|
56
|
+
revision policy.** The adapter does not provide persistence, transaction guards,
|
|
57
|
+
or domain approval. The host wire contract has no revision parameter; do not add
|
|
58
|
+
an invented field. Workflows requiring a human review/revision token should keep
|
|
59
|
+
that flow in the application, rather than force it into native settings. Tests
|
|
60
|
+
exercise an owning SQLite transaction, guarded update, rollback and retries.
|
|
61
|
+
|
|
62
|
+
Pass `openAiDisplayMetadata({ availableDisplayModes: ['inline', 'fullscreen'],
|
|
63
|
+
preferredDisplayMode: 'fullscreen' })` as a declared resource's `metadata`.
|
|
64
|
+
Resource security metadata and digest remain owned by app-MCP. `pip` is not part
|
|
65
|
+
of this pinned OpenAI display contract. Omitted fields preserve upstream defaults;
|
|
66
|
+
preferences remain hints.
|
|
67
|
+
|
|
68
|
+
`openAiOnboardingDeclaration('./skills/setup/SKILL.md')` returns only
|
|
69
|
+
`{ extensions: { 'com.openai': { onboardingSkill: ... } } }`. Merge that into the
|
|
70
|
+
plugin manifest; it does not install or execute the skill.
|
|
71
|
+
|
|
72
|
+
## Browser navigation and fallbacks
|
|
73
|
+
|
|
74
|
+
Import `./client`, pass the existing `McpAppBridge` to
|
|
75
|
+
`observeOpenAiNavigation({ bridge, resolveTool, onResult, onFallback })`, and call
|
|
76
|
+
the returned disposer when unmounting. It consumes bounded `rawHostContext` from
|
|
77
|
+
the bridge's validated origin/source and lifecycle, recognizes only
|
|
78
|
+
`openai/deepLink: { url }`, and calls the designated server tool with `{ url }`.
|
|
79
|
+
The domain tool must reject guessed or cross-tenant objects. Server applications
|
|
80
|
+
can use `resolveOpenAiNavigationTarget` to dispatch only an authorized read tool.
|
|
81
|
+
|
|
82
|
+
Repeated identical routes do not duplicate calls; new routes cancel old requests
|
|
83
|
+
and ignore stale replies. URLs are app-relative, fragment-free, traversal-free,
|
|
84
|
+
and reject protocol-relative, double-encoded and control-character forms. Treat
|
|
85
|
+
query parameters as untrusted input, never as identity or approval. The literal
|
|
86
|
+
query delimiter is separated before pathname decoding, so encoded question marks
|
|
87
|
+
cannot hide traversal segments. Traversal-like text in query values remains legal.
|
|
88
|
+
|
|
89
|
+
Only a rejected tool call or an `isError` result selects the `denied` fallback.
|
|
90
|
+
Consumer callback exceptions are not authorization failures and never retry a
|
|
91
|
+
fallback. Callbacks should handle their own rendering errors; uncaught exceptions
|
|
92
|
+
in asynchronous result/fallback callbacks surface as unhandled promise rejections.
|
|
93
|
+
|
|
94
|
+
`createOpenAiAppLink` emits pinned desktop (`codex`), mobile (`chatgpt`) or web
|
|
95
|
+
formats with separately encoded plugin/tool/path components. `openAiNavigationLink`
|
|
96
|
+
requires explicit `'present'` support to choose a native link; `'absent'`, unknown
|
|
97
|
+
or malformed support uses a configured HTTPS application URL. This support input
|
|
98
|
+
is a deployment/observed-host decision, **not an invented MCP capability field**.
|
|
99
|
+
Entrypoints/onboarding similarly have no client advertisement in the pinned spec;
|
|
100
|
+
keep ordinary tool/setup navigation available. Native structured settings are
|
|
101
|
+
optional: the same settings tools and application settings page remain usable.
|
|
102
|
+
|
|
103
|
+
`requestOpenAiDisplayMode` uses negotiated portable modes; missing/unknown modes
|
|
104
|
+
or a rejected request return `'inline'`. The UI must actually retain its inline
|
|
105
|
+
layout and render an ordinary link when navigation cannot be resolved.
|
|
106
|
+
|
|
107
|
+
## Context, messages and composer mentions
|
|
108
|
+
|
|
109
|
+
Browser consumers import `updateOpenAiModelContext` from `./context` and
|
|
110
|
+
`sendOpenAiMessage` from `./messages`. Server consumers import
|
|
111
|
+
`withOpenAiMentionSearch` and `resolveOpenAiMentionSelection` from `./mentions`.
|
|
112
|
+
These subpaths share the existing portable bridge and authorized app-MCP server.
|
|
113
|
+
|
|
114
|
+
`updateOpenAiModelContext({ bridge, value, native })` replaces context with
|
|
115
|
+
`{ text: { text, title?, thumbnail? }, structuredContent? }`. Text is nonempty and
|
|
116
|
+
at most 16384 characters; titles are limited to 512. Set `background: true` on the text value to emit assistant-only audience
|
|
117
|
+
metadata; background is a presentation
|
|
118
|
+
hint, **not a privacy boundary**: the model/provider still receives the context.
|
|
119
|
+
Only send data authorized for that recipient. Negotiated `openai/modelContext`
|
|
120
|
+
uses the supplied native callback. Missing support or native failure falls back
|
|
121
|
+
to portable text and structured content, without native metadata. Abort/disposal
|
|
122
|
+
prevents a late fallback.
|
|
123
|
+
|
|
124
|
+
`sendOpenAiMessage({ bridge, value, native })` accepts text and `target: 'active'`
|
|
125
|
+
(the default) or `'new'`. Active conversations can use portable text messages.
|
|
126
|
+
New conversations require negotiated `openai/message` and a native callback;
|
|
127
|
+
missing or malformed support rejects before any message reaches the active
|
|
128
|
+
conversation. Unknown targets/modalities fail closed and sends are not retried.
|
|
129
|
+
|
|
130
|
+
`withOpenAiMentionSearch(existingReadWorkflow)` declares app-only composer search
|
|
131
|
+
metadata and a `{ query }` input (at most 256 characters, including empty search).
|
|
132
|
+
Its owning handler returns `structuredContent: { items }`, with at most 25
|
|
133
|
+
`{ type: 'resource', resourceUri, title, subtitle?, icons? }` entries. Handles
|
|
134
|
+
are nonempty opaque strings of at most 2048 characters; titles/subtitles are
|
|
135
|
+
nonempty and bounded to 512. Each of at most eight icons requires `src` (2048),
|
|
136
|
+
with optional `mimeType` (128), up to eight nonempty `sizes` strings (32 each),
|
|
137
|
+
and `theme: 'light' | 'dark'`. Unknown fields and malformed values reject.
|
|
138
|
+
The published output schema describes these same closed objects and limits.
|
|
139
|
+
|
|
140
|
+
Search results are discovery hints, never authorization grants. Pass the exact
|
|
141
|
+
returned `resourceUri` as the existing selection workflow's argument through
|
|
142
|
+
`resolveOpenAiMentionSelection({ server, tool, arguments, principal })`. The
|
|
143
|
+
application supplies the trusted, freshly resolved principal; app-MCP policy
|
|
144
|
+
rechecks actor, active tenant, scopes and membership on every call, and the
|
|
145
|
+
owning workflow rechecks handle ownership. Never derive identity from a handle,
|
|
146
|
+
browser metadata or search-time permission. Guesses, cross-tenant calls and
|
|
147
|
+
revocation after search must not select private data. This helper adds no
|
|
148
|
+
selection handler, identity provider, persistence or authorization policy.
|
|
149
|
+
|
|
150
|
+
## Validation and evidence
|
|
151
|
+
|
|
152
|
+
Run `pnpm install`, then the documented dependency build and package gates:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
pnpm exec turbo build --filter=@happyvertical/smrt-mcp-openai...
|
|
156
|
+
pnpm --filter @happyvertical/smrt-mcp-openai typecheck
|
|
157
|
+
pnpm --filter @happyvertical/smrt-mcp-openai test
|
|
158
|
+
pnpm --filter @happyvertical/smrt-mcp-openai test:e2e
|
|
159
|
+
pnpm --filter @happyvertical/smrt-mcp-openai verify:pack
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The behavior/threat matrix is in [TEST-DESIGN.md](TEST-DESIGN.md). Protocol tests
|
|
163
|
+
use the actual SDK client and HTTP mount, including real loopback JWKS/MCP sockets
|
|
164
|
+
through the public M2 auth gateway with token, actor, tenant and revocation denials; the browser suite uses a real Chromium
|
|
165
|
+
sandboxed iframe with no-network CSP and built browser output. Packed exports
|
|
166
|
+
are checked for declaration and browser/server boundaries. Existing app-MCP full
|
|
167
|
+
suite/typecheck/pack and portable bridge gates also apply to integration changes.
|
|
168
|
+
The coordinator schedules all root gates and independent review.
|
|
169
|
+
|
|
170
|
+
**No actual OpenAI host compatibility is claimed.** The synthetic host does not
|
|
171
|
+
prove desktop, Work web, iOS, Android or account rollout support. No configured
|
|
172
|
+
OpenAI host connection or deployable registered synthetic plugin was provided;
|
|
173
|
+
record exact host/build/platform, auth profile, asset revision and observed
|
|
174
|
+
navigation/settings outcomes before claiming supported-host acceptance. The
|
|
175
|
+
upstream support matrix is expected support only; web refers to Work, excluding
|
|
176
|
+
classic ChatGPT. Actual host acceptance remains open for M8.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpAppBridge, ToolResult } from '@happyvertical/smrt-mcp-apps';
|
|
2
|
+
import { type OpenAiDisplayMode } from './validation.js';
|
|
3
|
+
export { appRelativeUrl, OPENAI_EXTENSIONS_REVISION } from './validation.js';
|
|
4
|
+
export type CapabilityState = 'present' | 'absent' | 'unknown';
|
|
5
|
+
/** Deployment/host-observation input, not an invented MCP wire capability. */
|
|
6
|
+
export declare function capabilityState(value: unknown): CapabilityState;
|
|
7
|
+
export interface OpenAiAppLink {
|
|
8
|
+
pluginId: string;
|
|
9
|
+
toolName: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
marketplace?: string;
|
|
12
|
+
platform: 'desktop' | 'mobile' | 'web';
|
|
13
|
+
}
|
|
14
|
+
export declare function createOpenAiAppLink(options: OpenAiAppLink): string;
|
|
15
|
+
/** No host-brand inference: absent/unverified support yields an ordinary application link. */
|
|
16
|
+
export declare function openAiNavigationLink(capability: unknown, link: OpenAiAppLink, fallbackUrl: string): string;
|
|
17
|
+
/** Display negotiation remains portable; unknown modes and host failures retain inline UI. */
|
|
18
|
+
export declare function requestOpenAiDisplayMode(bridge: McpAppBridge, requested: unknown): Promise<OpenAiDisplayMode>;
|
|
19
|
+
export declare function readOpenAiDeepLink(rawHostContext: unknown): string | undefined;
|
|
20
|
+
/** Observe inert host context, then request a domain-authorized read; never route to arbitrary URLs. */
|
|
21
|
+
export declare function observeOpenAiNavigation(options: {
|
|
22
|
+
bridge: McpAppBridge;
|
|
23
|
+
resolveTool: string;
|
|
24
|
+
onResult(result: ToolResult, url: string): void;
|
|
25
|
+
onFallback(reason: 'unavailable' | 'invalid' | 'denied'): void;
|
|
26
|
+
}): () => void;
|
|
27
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAKL,KAAK,iBAAiB,EAIvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC/D,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAE/D;AACD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;CACxC;AACD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAwBlE;AAOD,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,OAAO,EACnB,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAKR;AACD,8FAA8F;AAC9F,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,iBAAiB,CAAC,CAkB5B;AACD,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAOpB;AACD,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;CAChE,GAAG,MAAM,IAAI,CAkDb"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { appRelativeUrl, displayMode, json, keys, record, text, toolName, } from './validation.js';
|
|
2
|
+
export { appRelativeUrl, OPENAI_EXTENSIONS_REVISION } from './validation.js';
|
|
3
|
+
/** Deployment/host-observation input, not an invented MCP wire capability. */
|
|
4
|
+
export function capabilityState(value) {
|
|
5
|
+
return value === 'present' || value === 'absent' ? value : 'unknown';
|
|
6
|
+
}
|
|
7
|
+
export function createOpenAiAppLink(options) {
|
|
8
|
+
json(options);
|
|
9
|
+
keys(record(options), [
|
|
10
|
+
'pluginId',
|
|
11
|
+
'toolName',
|
|
12
|
+
'path',
|
|
13
|
+
'marketplace',
|
|
14
|
+
'platform',
|
|
15
|
+
]);
|
|
16
|
+
const plugin = encodeURIComponent(text(options.pluginId, 256));
|
|
17
|
+
const tool = encodeURIComponent(text(options.toolName, 128));
|
|
18
|
+
const path = appRelativeUrl(options.path ?? '/');
|
|
19
|
+
const marketplace = options.marketplace === undefined
|
|
20
|
+
? ''
|
|
21
|
+
: `@${encodeURIComponent(text(options.marketplace, 128))}`;
|
|
22
|
+
if (options.platform === 'web') {
|
|
23
|
+
if (marketplace)
|
|
24
|
+
throw new TypeError('Web links require a published ChatGPT plugin');
|
|
25
|
+
return `https://chatgpt.com/plugins/${plugin}/app/${tool}?path=${encodeURIComponent(path)}`;
|
|
26
|
+
}
|
|
27
|
+
if (options.platform !== 'desktop' && options.platform !== 'mobile')
|
|
28
|
+
throw new TypeError('Unsupported link platform');
|
|
29
|
+
return `${options.platform === 'desktop' ? 'codex' : 'chatgpt'}://plugins/${plugin}${marketplace}/app/${tool}?path=${encodeURIComponent(path)}`;
|
|
30
|
+
}
|
|
31
|
+
function appUrl(value) {
|
|
32
|
+
const parsed = new URL(text(value, 2048));
|
|
33
|
+
if (parsed.protocol !== 'https:' || parsed.username || parsed.password)
|
|
34
|
+
throw new TypeError('Expected credential-free HTTPS application URL');
|
|
35
|
+
return parsed.href;
|
|
36
|
+
}
|
|
37
|
+
/** No host-brand inference: absent/unverified support yields an ordinary application link. */
|
|
38
|
+
export function openAiNavigationLink(capability, link, fallbackUrl) {
|
|
39
|
+
const fallback = appUrl(fallbackUrl);
|
|
40
|
+
return capabilityState(capability) === 'present'
|
|
41
|
+
? createOpenAiAppLink(link)
|
|
42
|
+
: fallback;
|
|
43
|
+
}
|
|
44
|
+
/** Display negotiation remains portable; unknown modes and host failures retain inline UI. */
|
|
45
|
+
export async function requestOpenAiDisplayMode(bridge, requested) {
|
|
46
|
+
let mode;
|
|
47
|
+
try {
|
|
48
|
+
mode = displayMode(requested);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return 'inline';
|
|
52
|
+
}
|
|
53
|
+
const snapshot = bridge.snapshot;
|
|
54
|
+
if (snapshot.state !== 'ready' ||
|
|
55
|
+
!(snapshot.hostContext.availableDisplayModes ?? ['inline']).includes(mode))
|
|
56
|
+
return 'inline';
|
|
57
|
+
try {
|
|
58
|
+
return displayMode(await bridge.requestDisplayMode(mode));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 'inline';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function readOpenAiDeepLink(rawHostContext) {
|
|
65
|
+
json(rawHostContext);
|
|
66
|
+
const context = record(rawHostContext);
|
|
67
|
+
if (context['openai/deepLink'] === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
const link = record(context['openai/deepLink']);
|
|
70
|
+
keys(link, ['url']);
|
|
71
|
+
return appRelativeUrl(link.url);
|
|
72
|
+
}
|
|
73
|
+
/** Observe inert host context, then request a domain-authorized read; never route to arbitrary URLs. */
|
|
74
|
+
export function observeOpenAiNavigation(options) {
|
|
75
|
+
const name = toolName(options.resolveTool);
|
|
76
|
+
let last;
|
|
77
|
+
let generation = 0;
|
|
78
|
+
let disposed = false;
|
|
79
|
+
let pending;
|
|
80
|
+
const unsubscribe = options.bridge.subscribe((snapshot) => {
|
|
81
|
+
if (disposed || snapshot.state !== 'ready')
|
|
82
|
+
return;
|
|
83
|
+
let url;
|
|
84
|
+
try {
|
|
85
|
+
url = readOpenAiDeepLink(snapshot.rawHostContext);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
last = undefined;
|
|
89
|
+
generation++;
|
|
90
|
+
pending?.abort();
|
|
91
|
+
options.onFallback('invalid');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (url === undefined || url === last)
|
|
95
|
+
return;
|
|
96
|
+
last = url;
|
|
97
|
+
const current = ++generation;
|
|
98
|
+
pending?.abort();
|
|
99
|
+
if (!snapshot.hostCapabilities.serverTools) {
|
|
100
|
+
options.onFallback('unavailable');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
pending = new AbortController();
|
|
104
|
+
void options.bridge.callTool(name, { url }, pending.signal).then((result) => {
|
|
105
|
+
if (disposed || current !== generation || options.bridge.signal.aborted)
|
|
106
|
+
return;
|
|
107
|
+
if (result.isError)
|
|
108
|
+
options.onFallback('denied');
|
|
109
|
+
else
|
|
110
|
+
options.onResult(result, url);
|
|
111
|
+
}, () => {
|
|
112
|
+
if (!disposed &&
|
|
113
|
+
current === generation &&
|
|
114
|
+
!options.bridge.signal.aborted)
|
|
115
|
+
options.onFallback('denied');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return () => {
|
|
119
|
+
disposed = true;
|
|
120
|
+
generation++;
|
|
121
|
+
pending?.abort();
|
|
122
|
+
unsubscribe();
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,WAAW,EACX,IAAI,EACJ,IAAI,EAEJ,MAAM,EACN,IAAI,EACJ,QAAQ,GACT,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC;AAQD,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,IAAI,CAAC,OAAO,CAAC,CAAC;IACd,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;QACpB,UAAU;QACV,UAAU;QACV,MAAM;QACN,aAAa;QACb,UAAU;KACX,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IACjD,MAAM,WAAW,GACf,OAAO,CAAC,WAAW,KAAK,SAAS;QAC/B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IAC/D,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,IAAI,WAAW;YACb,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;QACtE,OAAO,+BAA+B,MAAM,QAAQ,IAAI,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;IAC9F,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QACjE,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACnD,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,cAAc,MAAM,GAAG,WAAW,QAAQ,IAAI,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;AAClJ,CAAC;AACD,SAAS,MAAM,CAAC,KAAc;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;QACpE,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AACD,8FAA8F;AAC9F,MAAM,UAAU,oBAAoB,CAClC,UAAmB,EACnB,IAAmB,EACnB,WAAmB;IAEnB,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IACrC,OAAO,eAAe,CAAC,UAAU,CAAC,KAAK,SAAS;QAC9C,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC;QAC3B,CAAC,CAAC,QAAQ,CAAC;AACf,CAAC;AACD,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAoB,EACpB,SAAkB;IAElB,IAAI,IAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IACE,QAAQ,CAAC,KAAK,KAAK,OAAO;QAC1B,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,qBAAqB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAE1E,OAAO,QAAQ,CAAC;IAClB,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AACD,MAAM,UAAU,kBAAkB,CAChC,cAAuB;IAEvB,IAAI,CAAC,cAAc,CAAC,CAAC;IACrB,MAAM,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,iBAAiB,CAAC,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACpB,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AACD,wGAAwG;AACxG,MAAM,UAAU,uBAAuB,CAAC,OAKvC;IACC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,IAAwB,CAAC;IAC7B,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAoC,CAAC;IACzC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;QACxD,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,OAAO;YAAE,OAAO;QACnD,IAAI,GAAuB,CAAC;QAC5B,IAAI,CAAC;YACH,GAAG,GAAG,kBAAkB,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,SAAS,CAAC;YACjB,UAAU,EAAE,CAAC;YACb,OAAO,EAAE,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO;QAC9C,IAAI,GAAG,GAAG,CAAC;QACX,MAAM,OAAO,GAAG,EAAE,UAAU,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC;YAC3C,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;QACD,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,KAAK,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAC9D,CAAC,MAAM,EAAE,EAAE;YACT,IAAI,QAAQ,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO;gBACrE,OAAO;YACT,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;;gBAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACrC,CAAC,EACD,GAAG,EAAE;YACH,IACE,CAAC,QAAQ;gBACT,OAAO,KAAK,UAAU;gBACtB,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO;gBAE9B,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,QAAQ,GAAG,IAAI,CAAC;QAChB,UAAU,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,CAAC;QACjB,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { McpAppBridge } from '@happyvertical/smrt-mcp-apps';
|
|
2
|
+
export declare const OPENAI_MODEL_CONTEXT_CAPABILITY = "openai/modelContext";
|
|
3
|
+
export interface OpenAiIcon {
|
|
4
|
+
src: string;
|
|
5
|
+
mimeType?: string;
|
|
6
|
+
sizes?: string[];
|
|
7
|
+
theme?: 'light' | 'dark';
|
|
8
|
+
}
|
|
9
|
+
export interface OpenAiContextText {
|
|
10
|
+
text: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
thumbnail?: OpenAiIcon;
|
|
13
|
+
/** Hidden from the person, never hidden from the model or provider. */
|
|
14
|
+
background?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface OpenAiModelContextUpdate {
|
|
17
|
+
content?: Array<Record<string, unknown>>;
|
|
18
|
+
structuredContent?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds only the pinned OpenAI extension fields. `_meta` crosses the provider
|
|
22
|
+
* boundary, so `background` is a visibility choice and never a privacy grant.
|
|
23
|
+
*/
|
|
24
|
+
export declare function openAiContextText(value: OpenAiContextText): Record<string, unknown>;
|
|
25
|
+
/** Validates a bounded, idempotent replacement for this app instance's context. */
|
|
26
|
+
export declare function openAiModelContextUpdate(value: {
|
|
27
|
+
text?: OpenAiContextText;
|
|
28
|
+
structuredContent?: Record<string, unknown>;
|
|
29
|
+
}): OpenAiModelContextUpdate;
|
|
30
|
+
/** Capability detection is explicit; unknown/malformed data cannot enable native metadata. */
|
|
31
|
+
export declare function hasOpenAiModelContext(rawCapabilities: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Sends a portable text/structured fallback through the existing bridge when
|
|
34
|
+
* native support was not negotiated. Native callers receive the exact wire
|
|
35
|
+
* payload and may transport title/thumbnail/background without leaking them
|
|
36
|
+
* into portable public types.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateOpenAiModelContext(options: {
|
|
39
|
+
bridge: McpAppBridge;
|
|
40
|
+
value: {
|
|
41
|
+
text?: OpenAiContextText;
|
|
42
|
+
structuredContent?: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
native?(params: OpenAiModelContextUpdate, signal?: AbortSignal): Promise<void>;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}): Promise<'native' | 'portable'>;
|
|
47
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,eAAO,MAAM,+BAA+B,wBAAwB,CAAC;AAErE,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAqBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C,GAAG,wBAAwB,CAc3B;AAED,8FAA8F;AAC9F,wBAAgB,qBAAqB,CAAC,eAAe,EAAE,OAAO,GAAG,OAAO,CAcvE;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACtD,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,iBAAiB,CAAC;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC7C,CAAC;IACF,MAAM,CAAC,CACL,MAAM,EAAE,wBAAwB,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CA0BjC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { json, keys, record, text } from './validation.js';
|
|
2
|
+
export const OPENAI_MODEL_CONTEXT_CAPABILITY = 'openai/modelContext';
|
|
3
|
+
function icon(value) {
|
|
4
|
+
json(value);
|
|
5
|
+
const input = record(value);
|
|
6
|
+
keys(input, ['src', 'mimeType', 'sizes', 'theme']);
|
|
7
|
+
const result = { src: text(input.src, 2048) };
|
|
8
|
+
if (input.mimeType !== undefined)
|
|
9
|
+
result.mimeType = text(input.mimeType, 128);
|
|
10
|
+
if (input.sizes !== undefined) {
|
|
11
|
+
if (!Array.isArray(input.sizes) || input.sizes.length > 8)
|
|
12
|
+
throw new TypeError('Invalid icon sizes');
|
|
13
|
+
result.sizes = input.sizes.map((size) => text(size, 32));
|
|
14
|
+
}
|
|
15
|
+
if (input.theme !== undefined) {
|
|
16
|
+
if (input.theme !== 'light' && input.theme !== 'dark')
|
|
17
|
+
throw new TypeError('Invalid icon theme');
|
|
18
|
+
result.theme = input.theme;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Builds only the pinned OpenAI extension fields. `_meta` crosses the provider
|
|
24
|
+
* boundary, so `background` is a visibility choice and never a privacy grant.
|
|
25
|
+
*/
|
|
26
|
+
export function openAiContextText(value) {
|
|
27
|
+
json(value);
|
|
28
|
+
const input = record(value);
|
|
29
|
+
keys(input, ['text', 'title', 'thumbnail', 'background']);
|
|
30
|
+
const result = {
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: text(input.text, 16384),
|
|
33
|
+
};
|
|
34
|
+
const meta = {};
|
|
35
|
+
if (input.title !== undefined)
|
|
36
|
+
meta['openai/title'] = text(input.title, 512);
|
|
37
|
+
if (input.thumbnail !== undefined)
|
|
38
|
+
meta['openai/thumbnail'] = icon(input.thumbnail);
|
|
39
|
+
if (Object.keys(meta).length)
|
|
40
|
+
result._meta = meta;
|
|
41
|
+
if (input.background !== undefined) {
|
|
42
|
+
if (typeof input.background !== 'boolean')
|
|
43
|
+
throw new TypeError('Expected background boolean');
|
|
44
|
+
if (input.background)
|
|
45
|
+
result.annotations = { audience: ['assistant'] };
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/** Validates a bounded, idempotent replacement for this app instance's context. */
|
|
50
|
+
export function openAiModelContextUpdate(value) {
|
|
51
|
+
json(value);
|
|
52
|
+
const input = record(value);
|
|
53
|
+
keys(input, ['text', 'structuredContent']);
|
|
54
|
+
if (input.text === undefined && input.structuredContent === undefined)
|
|
55
|
+
throw new TypeError('Model context requires content or structuredContent');
|
|
56
|
+
return {
|
|
57
|
+
...(input.text === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { content: [openAiContextText(input.text)] }),
|
|
60
|
+
...(input.structuredContent === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { structuredContent: record(input.structuredContent) }),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Capability detection is explicit; unknown/malformed data cannot enable native metadata. */
|
|
66
|
+
export function hasOpenAiModelContext(rawCapabilities) {
|
|
67
|
+
try {
|
|
68
|
+
json(rawCapabilities);
|
|
69
|
+
const capabilities = record(rawCapabilities);
|
|
70
|
+
const experimental = capabilities.experimental;
|
|
71
|
+
return (experimental !== undefined &&
|
|
72
|
+
record(experimental)[OPENAI_MODEL_CONTEXT_CAPABILITY] !== undefined &&
|
|
73
|
+
record(record(experimental)[OPENAI_MODEL_CONTEXT_CAPABILITY]) !==
|
|
74
|
+
undefined);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Sends a portable text/structured fallback through the existing bridge when
|
|
82
|
+
* native support was not negotiated. Native callers receive the exact wire
|
|
83
|
+
* payload and may transport title/thumbnail/background without leaking them
|
|
84
|
+
* into portable public types.
|
|
85
|
+
*/
|
|
86
|
+
export async function updateOpenAiModelContext(options) {
|
|
87
|
+
const params = openAiModelContextUpdate(options.value);
|
|
88
|
+
if (options.native &&
|
|
89
|
+
hasOpenAiModelContext(options.bridge.snapshot.rawHostCapabilities)) {
|
|
90
|
+
try {
|
|
91
|
+
await options.native(params, options.signal);
|
|
92
|
+
return 'native';
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// An aborted or disposed request must not be revived on the portable path.
|
|
96
|
+
if (options.signal?.aborted)
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
await options.bridge.updateModelContext({
|
|
101
|
+
...(options.value.text === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { text: options.value.text.text }),
|
|
104
|
+
...(params.structuredContent === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { structuredContent: params.structuredContent }),
|
|
107
|
+
}, options.signal);
|
|
108
|
+
return 'portable';
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type McpAppPrincipal, type McpAppServer, type McpWorkflowToolDefinition } from '@happyvertical/smrt-app-mcp';
|
|
2
|
+
import type { MCPTool, ToolJsonSchema } from '@happyvertical/smrt-core/generators/mcp';
|
|
3
|
+
import { type SettingsSchema } from './settings.js';
|
|
4
|
+
import { type OpenAiDisplayMode } from './validation.js';
|
|
5
|
+
export type { SettingSchema, SettingsGroup, SettingsReadResult, SettingsSchema, } from './settings.js';
|
|
6
|
+
export { validateSettingsRead, validateSettingsSchema, validateSettingsValues, } from './settings.js';
|
|
7
|
+
export type { OpenAiDisplayMode } from './validation.js';
|
|
8
|
+
export { appRelativeUrl, OPENAI_EXTENSIONS_REVISION } from './validation.js';
|
|
9
|
+
export declare const EMPTY_ARGUMENTS_SCHEMA: ToolJsonSchema;
|
|
10
|
+
/** Annotate an existing authorized read workflow; no alternative dispatch path. */
|
|
11
|
+
export declare function withOpenAiEntrypoints(definition: McpWorkflowToolDefinition, entrypoints: readonly ('global' | 'thread')[]): McpWorkflowToolDefinition;
|
|
12
|
+
/** Resource content metadata (not tool metadata). Omitted fields preserve upstream defaults. */
|
|
13
|
+
export declare function openAiDisplayMetadata(options?: {
|
|
14
|
+
availableDisplayModes?: OpenAiDisplayMode[];
|
|
15
|
+
preferredDisplayMode?: OpenAiDisplayMode;
|
|
16
|
+
}): Record<string, unknown>;
|
|
17
|
+
/** Manifest declaration only. M7 validates the referenced packaged asset. */
|
|
18
|
+
export declare function openAiOnboardingDeclaration(skill: string): {
|
|
19
|
+
extensions: {
|
|
20
|
+
'com.openai': {
|
|
21
|
+
onboardingSkill: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export interface OpenAiSettingsBinding {
|
|
26
|
+
readTool: string;
|
|
27
|
+
updateTool: string;
|
|
28
|
+
workflows: [McpWorkflowToolDefinition, McpWorkflowToolDefinition];
|
|
29
|
+
/** Pass to the app-MCP protocol extension callback; input must be its authorized catalog. */
|
|
30
|
+
extensions(tools: readonly MCPTool[]): Record<string, Record<string, unknown>>;
|
|
31
|
+
}
|
|
32
|
+
/** Wrap existing read/atomic-assignment workflows. Their execution and authorization remain app-owned. */
|
|
33
|
+
export declare function bindOpenAiSettings(options: {
|
|
34
|
+
schema: SettingsSchema;
|
|
35
|
+
read: McpWorkflowToolDefinition;
|
|
36
|
+
update: McpWorkflowToolDefinition;
|
|
37
|
+
/** Resolve the same server after composition, for authorized layout tool references. */
|
|
38
|
+
server: () => McpAppServer;
|
|
39
|
+
}): OpenAiSettingsBinding;
|
|
40
|
+
/** Resolve untrusted route data through the existing server policy and domain read handler. */
|
|
41
|
+
export declare function resolveOpenAiNavigationTarget(options: {
|
|
42
|
+
server: McpAppServer;
|
|
43
|
+
tool: string;
|
|
44
|
+
url: string;
|
|
45
|
+
principal: McpAppPrincipal | null;
|
|
46
|
+
}): Promise<import("@happyvertical/smrt-core").MCPResponse>;
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACf,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,KAAK,iBAAiB,EAIvB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7E,eAAO,MAAM,sBAAsB,EAAE,cAIpC,CAAC;AAyBF,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,yBAAyB,EACrC,WAAW,EAAE,SAAS,CAAC,QAAQ,GAAG,QAAQ,CAAC,EAAE,GAC5C,yBAAyB,CA0B3B;AACD,gGAAgG;AAChG,wBAAgB,qBAAqB,CACnC,OAAO,GAAE;IACP,qBAAqB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC5C,oBAAoB,CAAC,EAAE,iBAAiB,CAAC;CACrC,GACL,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsBzB;AACD,6EAA6E;AAC7E,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG;IAC1D,UAAU,EAAE;QAAE,YAAY,EAAE;YAAE,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CAC3D,CAKA;AACD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;IAClE,6FAA6F;IAC7F,UAAU,CACR,KAAK,EAAE,SAAS,OAAO,EAAE,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC5C;AACD,0GAA0G;AAC1G,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,yBAAyB,CAAC;IAChC,MAAM,EAAE,yBAAyB,CAAC;IAClC,wFAAwF;IACxF,MAAM,EAAE,MAAM,YAAY,CAAC;CAC5B,GAAG,qBAAqB,CAqGxB;AACD,+FAA+F;AAC/F,wBAAsB,6BAA6B,CAAC,OAAO,EAAE;IAC3D,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC,2DAcA"}
|