@happyvertical/smrt-mcp-openai 0.0.0-stage → 0.51.39
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/FILES.md +77 -0
- package/FORMS-TEST-DESIGN.md +42 -0
- package/FORMS.md +112 -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/file-contracts.d.ts +34 -0
- package/dist/file-contracts.d.ts.map +1 -0
- package/dist/file-contracts.js +98 -0
- package/dist/file-contracts.js.map +1 -0
- package/dist/files-server.d.ts +6 -0
- package/dist/files-server.d.ts.map +1 -0
- package/dist/files-server.js +45 -0
- package/dist/files-server.js.map +1 -0
- package/dist/files.d.ts +38 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +288 -0
- package/dist/files.js.map +1 -0
- package/dist/forms-server.d.ts +53 -0
- package/dist/forms-server.d.ts.map +1 -0
- package/dist/forms-server.js +115 -0
- package/dist/forms-server.js.map +1 -0
- package/dist/forms-view.d.ts +14 -0
- package/dist/forms-view.d.ts.map +1 -0
- package/dist/forms-view.js +213 -0
- package/dist/forms-view.js.map +1 -0
- package/dist/forms.d.ts +118 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +389 -0
- package/dist/forms.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 +149 -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 +15 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +121 -0
- package/dist/validation.js.map +1 -0
- package/package.json +96 -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/FILES.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Optional native files
|
|
2
|
+
|
|
3
|
+
Import browser APIs from `@happyvertical/smrt-mcp-openai/files` and server
|
|
4
|
+
annotation from `@happyvertical/smrt-mcp-openai/files/server`. Neither is loaded
|
|
5
|
+
by ordinary tools. This is a bounded small-file adapter, not a filesystem.
|
|
6
|
+
|
|
7
|
+
`withOpenAiFileEntrypoint(existingWorkflow, ['.txt'])` adds the pinned file
|
|
8
|
+
entrypoint and validates `{file:{name,resourceUri}}`. The existing read workflow
|
|
9
|
+
must resolve the reference through its provider and current trusted principal,
|
|
10
|
+
owner and active tenant. A handle, filename or `_meta["openai/resource"].path`
|
|
11
|
+
is never authority. Remote servers must never read a host path as their own disk.
|
|
12
|
+
|
|
13
|
+
After the portable bridge connects, create `OpenAiFileSession` with its initial
|
|
14
|
+
file input, an existing `authorityTool`, a `fallbackTool`, `maxBytes` (1–16384)
|
|
15
|
+
and explicit MIME allowlist. The authority tool receives `{file,operation}` and
|
|
16
|
+
must reject anonymous, other-owner, other-tenant, guessed, revoked and stale grants
|
|
17
|
+
using the application's existing policy/provider APIs. Successful empty tool
|
|
18
|
+
results mean the application authorized that operation; the adapter does not
|
|
19
|
+
supply a second grant database. No browser-supplied identity is accepted.
|
|
20
|
+
|
|
21
|
+
The host independently enforces the opaque grant. Reads request the selected
|
|
22
|
+
text/blob representation and validate exact URI, MIME and decoded byte count.
|
|
23
|
+
Each operation checks application authority; reads recheck after the host result.
|
|
24
|
+
Writes require a fresh read with `writable:true` and a nonempty ETag, always send
|
|
25
|
+
`ifMatch`, reject parallel writes and never retry. Every outcome, including saved,
|
|
26
|
+
conflict, too-large or unknown upstream failure, requires another authorized read.
|
|
27
|
+
A host ETag is a compare/write guard, not an idempotency token.
|
|
28
|
+
|
|
29
|
+
One bounded subscription refreshes only the initial URI and rechecks authority.
|
|
30
|
+
Refreshes coalesce, revocation disposes the session, and stale/disposed results
|
|
31
|
+
cannot update content. Dispose on UI unmount. Reconnect or a new file needs a new
|
|
32
|
+
session; no handle migration is attempted. Unsubscribe is best effort during
|
|
33
|
+
teardown, while local callbacks and authority end immediately.
|
|
34
|
+
|
|
35
|
+
Absent/unknown resource capability uses the ordinary `fallbackTool` workflow
|
|
36
|
+
for read/import/open/download or editor launch. It receives `{file,operation}`;
|
|
37
|
+
it must return usable text/links/UI through the same application policy. The
|
|
38
|
+
fallback does not silently replay a proposed native write. The user completes
|
|
39
|
+
editing through the ordinary application workflow.
|
|
40
|
+
|
|
41
|
+
`openOpenAiFile` requires explicit application confirmation, an existing provider
|
|
42
|
+
tool resolving the selected object, and an exact trusted `allowedPaths` list. The
|
|
43
|
+
host open request uses only the authorized provider's returned path. It never
|
|
44
|
+
accepts a caller's raw path or reads that path on the server. Without native file
|
|
45
|
+
opening, the provider tool's ordinary open/download result is returned. A remote
|
|
46
|
+
provider should return a normal authorized download workflow, not desktop paths.
|
|
47
|
+
|
|
48
|
+
## Protocol and evidence
|
|
49
|
+
|
|
50
|
+
Pinned OpenAI source revision `e314720a0daac326217d1f123fcf51647868fa9f`:
|
|
51
|
+
`docs/spec.md`, `typescript/src/shared/file-entrypoint.ts`,
|
|
52
|
+
`typescript/src/app/resources.ts`, `typescript/src/app/files.ts`, and
|
|
53
|
+
`typescript/src/shared/resources.ts`. Full Zod unions define wire fields.
|
|
54
|
+
Native browser capability keys are `experimental["openai/resource"]` and
|
|
55
|
+
`experimental["openai/files"]`, each `{}`; neither has a version field. The
|
|
56
|
+
adapter uses the existing portable bridge's origin/source/lifecycle enforcement.
|
|
57
|
+
There is no SDK-v1 helper, second server, or fabricated server subscription.
|
|
58
|
+
Host resources are intercepted by the browser host; server SDK-v2 HTTP carries
|
|
59
|
+
ordinary principal-authorized entrypoint tools and fallbacks (2026-07-28).
|
|
60
|
+
|
|
61
|
+
Run build, typecheck, test, test:e2e, test:files:e2e, verify:pack and
|
|
62
|
+
verify:files:pack with `pnpm --filter @happyvertical/smrt-mcp-openai` after normal
|
|
63
|
+
install and dependency builds. FILES-TEST-DESIGN.md maps adversarial coverage.
|
|
64
|
+
Synthetic HTTP/browser fixtures use no real files or user data. Actual OpenAI
|
|
65
|
+
product/build/platform access is unavailable here; these tests do not establish
|
|
66
|
+
live OpenAI host compatibility or permit advertising observed host support.
|
|
67
|
+
|
|
68
|
+
Subscription lifecycle regression (#3214 review round 1): notifications during a
|
|
69
|
+
pending write coalesce into a fresh authorized read after the write settles.
|
|
70
|
+
Superseded refreshes retry a read without disposing the session; authority and
|
|
71
|
+
validation failures still terminate it. Disposal dispatches best-effort host
|
|
72
|
+
unsubscribe and releases local registration synchronously, allowing immediate
|
|
73
|
+
replacement even when the host never replies. Deterministic session tests cover
|
|
74
|
+
delayed read/write completion, the refreshed ETag, revocation, and replacement
|
|
75
|
+
while unsubscribe is withheld.
|
|
76
|
+
|
|
77
|
+
Resource URIs are preserved verbatim and reject Windows drive prefixes as well as URLs and filesystem paths. Matching resource-update notifications invalidate in-flight reads before callbacks publish content; refresh retries still obtain fresh authority. A session invalidated by the bridge's initial synchronous snapshot immediately releases its lifecycle listener.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# M6d form and resource-selection validation contract
|
|
2
|
+
|
|
3
|
+
Refs #3216. Risk: high — asynchronous authorization and approval integrity.
|
|
4
|
+
Source of wire truth: OpenAI mcp-extensions e314720a0daac326217d1f123fcf51647868fa9f,
|
|
5
|
+
full server/forms/{schema,fields,file-picker,elicitation}.ts. No generated JSON schema artifact.
|
|
6
|
+
|
|
7
|
+
| Behavior / reachable trigger | Positive | Negative / failure | Actor/context | Executor/transaction | Runtime/dialect | External edge | Test level / command |
|
|
8
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
9
|
+
| Form declaration before suspension | Primitive, select, array, resource variants | Unknown/oversized/accessor/prototype/schema injection, invalid defaults | Server-owned declaration | N/A: pure validation | Node/browser | Pinned complete schema unions, explicitly narrowed safety profile | Unit / pnpm --filter @happyvertical/smrt-mcp-openai test |
|
|
10
|
+
| Form reply before use | Exact accept content, cancel, decline | Missing/unknown fields, wrong type/options/resource, oversized content | Untrusted host/app reply | N/A: validation precedes authoritative handler | Node/browser | Exact reply union | Unit / same |
|
|
11
|
+
| Durable form continuation | Owner+tenant+record/revision/input key survive restart | Wrong owner/tenant/request/revision, duplicate/concurrent/cancelled reply | Verified application actor, active tenant | Existing McpTaskStore job row and CAS; no new store | SQLite/PostgreSQL | Existing M5 continuation | Integration / same with SMRT_TEST_POSTGRES_URL for PostgreSQL |
|
|
12
|
+
| Live authority and application apply | Fresh authorization at start/resume/apply | Revocation before/after reply, callback failure, unknown external outcome | Verified actor × ownership × tenant | Existing TaskRunner, maxAttempts=1, owning handler transaction | SQLite/PostgreSQL | M5 authority callback; form answer never approval | Integration / same |
|
|
13
|
+
| Capability/native transport decision | Explicit usable application form fallback | Absent/unknown capability, unsupported SDK path, upstream failure | Synthetic SDK client | Same authoritative workflow | SDK v2 HTTP | openai/elicitation create/MRTR support checked explicitly | Integration / same |
|
|
14
|
+
| Functional form presentation | Keyboard usable accept/cancel/decline, escaped options | Inert injected text, invalid values | Synthetic browser user | Submit to owning application workflow; no browser authority | Chromium | Application form fallback, no host certification | Browser / pnpm --filter @happyvertical/smrt-mcp-openai test:forms:e2e |
|
|
15
|
+
| Package boundaries | Packed forms browser and server entries load | SDK v1/server imports in browser | Package consumer | N/A: static bundle/load | Node/browser | Published explicit subpaths | Pack / pnpm --filter @happyvertical/smrt-mcp-openai verify:forms:pack |
|
|
16
|
+
|
|
17
|
+
Full root gates and final independent review are coordinator-owned after #3205 repair.
|
|
18
|
+
Actual OpenAI host verification remains M8; synthetic tests cannot satisfy it.
|
|
19
|
+
|
|
20
|
+
Accepted review regression: for both multi-select and free-text arrays, untouched
|
|
21
|
+
optional fields without defaults are omitted; editing then clearing submits an
|
|
22
|
+
explicit empty array, accepted at minItems=0 and rejected at minItems=1. Untouched
|
|
23
|
+
required arrays still undergo minItems validation. Browser fixtures cover each
|
|
24
|
+
case; the two optional-omission cases fail against the pre-fix renderer.
|
|
25
|
+
|
|
26
|
+
## PR3283 accepted round-3 corrections
|
|
27
|
+
|
|
28
|
+
| Invariant | Reachable trigger | Positive | Negative/failure | Actor/context and executor | Runtime/contract edge | Level and command |
|
|
29
|
+
|---|---|---|---|---|---|---|
|
|
30
|
+
| Inert shared JSON | form schema/reply and shared native/context/message/mention/settings/file parsers | ordinary nested JSON; validation returns void | array getter/iterator/custom prototype, hidden getter/toJSON, inherited toJSON: zero callbacks; sparse/custom/symbol fields rejected | untrusted caller; no domain executor | Node and browser; pinned bounded JSON schema, unknown non-JSON rejected | full `test`, installed forms/shared-boundary probes |
|
|
31
|
+
| Reversible string arrays | free array form editor defaults and edited values | newline item, empty item, >4096 total with bounded items | invalid JSON, non-string/oversized item, minItems empty | local form input; submission spy/HTTP fixture only, no domain approval | Chromium; existing 64-item/4096-item/65536-byte profile | `test:forms:e2e`, installed validator |
|
|
32
|
+
| Optional empty choice | enum/oneOf selection/default | defined empty choice survives | untouched optional text omitted | local form; authenticated route still owns execution | Chromium select placeholder vs explicit empty option | `test:forms:e2e` |
|
|
33
|
+
| Real date-time | reply/default string validation | leap day, fractional second, offsets crossing date boundary | February31/nonleapFebruary29/April31, hour24, minute/second60, offset overflow | untrusted input; no persistence mutation | Node/public installed API; existing RFC3339-style profile | `test`, installed forms probe |
|
|
34
|
+
|
|
35
|
+
Baseline production48bc with test-only overlays: descriptor/date unit regressions
|
|
36
|
+
and actual Chromium round-trip/choice regressions fail before fixes; overlay hashes
|
|
37
|
+
and exact logs are retained externally in round-3-pr-feedback. Shared `json()`
|
|
38
|
+
callers were audited in validation, index, client, settings, context, messages,
|
|
39
|
+
mentions, file-contracts, files and forms-server: all call the boundary before
|
|
40
|
+
interpreting wire values; stricter descriptor rejection has full owning-package
|
|
41
|
+
unit/browser/installed coverage. SQL/PG/root-wide replay is N/A for these parser
|
|
42
|
+
and presentation-only corrections; existing durable authority remains unchanged.
|
package/FORMS.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Optional extended forms and resource selection
|
|
2
|
+
|
|
3
|
+
`@happyvertical/smrt-mcp-openai/forms` validates the bounded OpenAI wire profile.
|
|
4
|
+
`/forms/view` renders a functional application form without host capabilities.
|
|
5
|
+
`/forms/server` composes that input with the existing durable MCP workflow.
|
|
6
|
+
No form answer supplies identity, file permission, or domain approval.
|
|
7
|
+
|
|
8
|
+
## Pinned contract and native limitation
|
|
9
|
+
|
|
10
|
+
Wire source: OpenAI mcp-extensions
|
|
11
|
+
[e314720a0daac326217d1f123fcf51647868fa9f](https://github.com/openai/mcp-extensions/tree/e314720a0daac326217d1f123fcf51647868fa9f/typescript/src/server/forms):
|
|
12
|
+
full `fields.ts`, `file-picker.ts`, `schema.ts`, and `elicitation.ts` were inspected.
|
|
13
|
+
The repository supplies TypeScript/Zod source, not a generated JSON schema artifact.
|
|
14
|
+
Official [MCP guidance](https://developers.openai.com/plugins/build/mcp-server)
|
|
15
|
+
requires server-side authorization and says elicitation collects information,
|
|
16
|
+
not credentials or authentication bypasses.
|
|
17
|
+
|
|
18
|
+
The pinned extension advertises `extensions['openai/elicitation'].form` and calls
|
|
19
|
+
`openai/elicitation/create`. SMRT's scoped `@modelcontextprotocol/server` 2.0.0,
|
|
20
|
+
protocol 2026-07-28, has a public `InputRequest` union of `CreateMessageRequest |
|
|
21
|
+
ListRootsRequest | ElicitRequest`. `inputRequired.elicit` emits only
|
|
22
|
+
`elicitation/create`; `ServerContext.mcpReq.elicitInput` explicitly throws on
|
|
23
|
+
2026-07-28. The public MRTR union does not include the OpenAI method. The upstream
|
|
24
|
+
helper uses monolithic SDK v1 and is not an acceptable substitute. Therefore
|
|
25
|
+
`openAiFormSupport` returns `native:false` even with a present host capability,
|
|
26
|
+
with `sdk-mrtr-unsupported`. Absent and unknown capabilities also select the
|
|
27
|
+
functional application-form fallback. No OpenAI extension capability is
|
|
28
|
+
advertised, no callback is kept alive, and the stateless mount is unchanged.
|
|
29
|
+
Actual OpenAI host support is unverified and remains the M8 acceptance gate.
|
|
30
|
+
|
|
31
|
+
## Bounded wire profile
|
|
32
|
+
|
|
33
|
+
Supported fields cover boolean, number/integer, string, titled/legacy single
|
|
34
|
+
select, titled/untitled multi-select, string arrays, suggestions, thumbnail/preview
|
|
35
|
+
metadata, and file/resource picker strings or arrays. Replies are exactly
|
|
36
|
+
`{action:'accept',content:{...}}`, `{action:'cancel'}`, or `{action:'decline'}`.
|
|
37
|
+
The parser rejects unknown fields instead of stripping semantic instructions.
|
|
38
|
+
Limits: 64 fields/options/selections, 4096-character values, 256-character resource
|
|
39
|
+
names, 2048-character URIs, 65536-byte inert JSON budget, depth 16. Numeric bounds
|
|
40
|
+
must be finite; integer answers must be safe integers. Required/enum/default and
|
|
41
|
+
resource-selection rules are checked without coercion or inserted defaults.
|
|
42
|
+
|
|
43
|
+
Deliberate stricter safety profile: arbitrary regex `pattern` is rejected rather
|
|
44
|
+
than evaluated; prototype-sensitive names and accessors are rejected; images
|
|
45
|
+
must be HTTPS without credentials or bounded raster data URIs. The fallback
|
|
46
|
+
renderer does not fetch images. Resources must use opaque absolute URIs;
|
|
47
|
+
`file:`, `data:`, and `javascript:` are rejected, and a remote application cannot
|
|
48
|
+
interpret a desktop path. The owning grant provider must authorize every selected
|
|
49
|
+
URI, including offered options and implicit/user-supplied selections. Selection
|
|
50
|
+
is input only. Unknown resource metadata does not become permission.
|
|
51
|
+
|
|
52
|
+
## Durable application integration
|
|
53
|
+
|
|
54
|
+
Inside the existing task runner, call `continueOpenAiForm` with a server-owned
|
|
55
|
+
`McpWorkflowContinuation` (`recordId`, immutable `revision`, unique `inputKey`,
|
|
56
|
+
HTTPS or loopback `reviewUrl`), schema, fresh `authorizeApply`, and `applyInput`.
|
|
57
|
+
Forms containing pickers also require `authorizeResource`. The task must already
|
|
58
|
+
have the exact same continuation binding. M5 persists its owner and active tenant,
|
|
59
|
+
checks live task authority at start/resume, and uses one job row for suspension,
|
|
60
|
+
first-response CAS, cancellation, and execution. No new approval store exists.
|
|
61
|
+
|
|
62
|
+
The authenticated application form reads the descriptor through
|
|
63
|
+
`createMcpContinuationTool` or its actor/tenant-bound `McpTaskStore`, renders it
|
|
64
|
+
with `renderOpenAiForm`, then calls `submitOpenAiForm` on its server. Supply that
|
|
65
|
+
verified actor's tenant-bound store and a policy callback which resolves fresh
|
|
66
|
+
application authority. Never derive either from submitted form values. Submit
|
|
67
|
+
validates the stored schema, a canonical SHA-256 schema fingerprint, and exact binding before persisting a bound reply;
|
|
68
|
+
the worker validates again, checks selected resource grants, then reauthorizes both application access and active task ownership after asynchronous checks
|
|
69
|
+
before `applyInput`. Direct `tasks/update` cannot bypass worker validation.
|
|
70
|
+
|
|
71
|
+
A submitted reply is not a completed operation. Concurrent replies use the
|
|
72
|
+
existing store's first-response-wins CAS; stale/cancelled/consumed requests fail
|
|
73
|
+
closed. Poll the task for its authoritative result. Worker restart replays the
|
|
74
|
+
handler, so all code before suspension must be read-only or use the existing
|
|
75
|
+
owning action/runOnce idempotency API. Remote jobs use `maxAttempts:1`; unknown
|
|
76
|
+
external outcomes fail for manual reconciliation, not automatic replay.
|
|
77
|
+
`applyInput` must enforce domain review and immutable revision in its owning
|
|
78
|
+
transaction, and external access must recheck applicable authority at its own
|
|
79
|
+
boundary. Iolaus retains its dedicated human review page and no employer
|
|
80
|
+
transmission occurs in fixtures. Accepting a form does not approve an application.
|
|
81
|
+
|
|
82
|
+
Run package build, typecheck, test, test:forms:e2e and verify:forms:pack. See
|
|
83
|
+
[FORMS-TEST-DESIGN.md](FORMS-TEST-DESIGN.md) for the evidence contract. Tests use
|
|
84
|
+
synthetic data only; PostgreSQL authority tests use `SMRT_TEST_POSTGRES_URL`.
|
|
85
|
+
|
|
86
|
+
In the application form, an untouched optional array without a default is
|
|
87
|
+
omitted from the reply. Editing then clearing an array submits an explicit
|
|
88
|
+
empty array, which must satisfy `minItems`. Required arrays and declared
|
|
89
|
+
defaults always remain subject to the schema's normal reply validation.
|
|
90
|
+
|
|
91
|
+
### Inert inputs and reversible fallback values
|
|
92
|
+
|
|
93
|
+
Form schemas and replies are interpreted and serialized from a detached snapshot
|
|
94
|
+
of own enumerable data descriptors. Accessors, sparse/custom-prototype arrays,
|
|
95
|
+
custom array properties/iterators, hidden fields and serialization hooks are
|
|
96
|
+
rejected without invoking supplied callbacks. The shared JSON validator retains
|
|
97
|
+
its validation-only return contract and existing depth/node/byte bounds; ordinary
|
|
98
|
+
JSON data remains supported. Native, context, messages, mentions, settings, file
|
|
99
|
+
and continuation callers share this boundary. Host callbacks/functions belong in
|
|
100
|
+
adapter options, never in wire JSON.
|
|
101
|
+
|
|
102
|
+
The free string-array fallback uses an explicitly labeled JSON array editor:
|
|
103
|
+
`["first", "second"]`, `["a\nb"]`, `[""]` and `[]` remain distinct. Each item and
|
|
104
|
+
the complete reply retain their existing limits; the editor allows up to 65,536
|
|
105
|
+
characters and wire validation enforces the byte limit. Invalid JSON remains
|
|
106
|
+
editable and does not submit. Untouched optional arrays/text remain omitted;
|
|
107
|
+
intentional empty choices and empty defaults are retained. A blank array editor
|
|
108
|
+
means an intentional empty list after interaction, preserving minItems checks.
|
|
109
|
+
|
|
110
|
+
Date-time replies require a real calendar date, hours 00–23, minutes/seconds
|
|
111
|
+
00–59 and offset hours/minutes 00–23/00–59, in the existing `T` and `Z`/offset
|
|
112
|
+
profile. Invalid dates normalized by JavaScript's Date parser are rejected.
|
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":"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"}
|