@raybase/openai-subscription 0.0.0 → 0.1.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/ARCHITECTURE.md +80 -0
- package/LICENSE +21 -0
- package/README.md +198 -2
- package/compatibility/fixtures/device-usercode.json +5 -0
- package/compatibility/fixtures/responses-function-call.sse +23 -0
- package/compatibility/fixtures/responses-image-input.sse +15 -0
- package/compatibility/fixtures/responses-image-request.json +32 -0
- package/compatibility/fixtures/responses-invalid-function-parameters.json +8 -0
- package/compatibility/fixtures/responses-invalid-json-schema.json +8 -0
- package/compatibility/fixtures/responses-mixed.sse +41 -0
- package/compatibility/fixtures/responses-multi-tool.sse +35 -0
- package/compatibility/fixtures/responses.sse +26 -0
- package/compatibility/manifest.json +50 -0
- package/dist/crypto.d.ts +12 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +86 -0
- package/dist/crypto.js.map +1 -0
- package/dist/health.d.ts +9 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +22 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +341 -0
- package/dist/module.js.map +1 -0
- package/dist/provider.d.ts +8 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +361 -0
- package/dist/provider.js.map +1 -0
- package/dist/store.d.ts +51 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +342 -0
- package/dist/store.js.map +1 -0
- package/dist/system-tables.d.ts +7 -0
- package/dist/system-tables.d.ts.map +1 -0
- package/dist/system-tables.js +59 -0
- package/dist/system-tables.js.map +1 -0
- package/dist/testing.d.ts +4 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +4 -0
- package/dist/testing.js.map +1 -0
- package/dist/transport.d.ts +104 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +1115 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -8
- package/skills/raybase-openai-subscription/SKILL.md +57 -0
- package/skills/raybase-openai-subscription/references/compatibility-and-testing.md +129 -0
- package/skills/raybase-openai-subscription/references/module-contract.md +133 -0
- package/skills/raybase-openai-subscription/references/structured-outputs.md +159 -0
- package/templates/astro-emdash/.agents/skills/raybase-openai-subscription/SKILL.md +57 -0
- package/templates/astro-emdash/.agents/skills/raybase-openai-subscription/references/compatibility-and-testing.md +129 -0
- package/templates/astro-emdash/.agents/skills/raybase-openai-subscription/references/module-contract.md +133 -0
- package/templates/astro-emdash/.agents/skills/raybase-openai-subscription/references/structured-outputs.md +159 -0
- package/templates/astro-emdash/src/openai-subscription.ts +11 -0
- package/templates/react-router/.agents/skills/raybase-openai-subscription/SKILL.md +57 -0
- package/templates/react-router/.agents/skills/raybase-openai-subscription/references/compatibility-and-testing.md +129 -0
- package/templates/react-router/.agents/skills/raybase-openai-subscription/references/module-contract.md +133 -0
- package/templates/react-router/.agents/skills/raybase-openai-subscription/references/structured-outputs.md +159 -0
- package/templates/react-router/app/openai-subscription.ts +11 -0
- package/templates/tanstack-start/.agents/skills/raybase-openai-subscription/SKILL.md +57 -0
- package/templates/tanstack-start/.agents/skills/raybase-openai-subscription/references/compatibility-and-testing.md +129 -0
- package/templates/tanstack-start/.agents/skills/raybase-openai-subscription/references/module-contract.md +133 -0
- package/templates/tanstack-start/.agents/skills/raybase-openai-subscription/references/structured-outputs.md +159 -0
- package/templates/tanstack-start/src/openai-subscription.ts +11 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Structured outputs and strict tool schemas
|
|
2
|
+
|
|
3
|
+
This is the self-contained schema contract developers need when sending
|
|
4
|
+
structured output through the module. It is the standard OpenAI Structured
|
|
5
|
+
Outputs subset, not a subscription-specific alternative.
|
|
6
|
+
|
|
7
|
+
## Two request shapes, one strict subset
|
|
8
|
+
|
|
9
|
+
Final structured output in the Responses API uses `text.format`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"model": "MODEL_ID",
|
|
14
|
+
"input": "Extract project metadata",
|
|
15
|
+
"text": {
|
|
16
|
+
"format": {
|
|
17
|
+
"type": "json_schema",
|
|
18
|
+
"name": "project_metadata",
|
|
19
|
+
"strict": true,
|
|
20
|
+
"schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": { "name": { "type": "string" } },
|
|
23
|
+
"required": ["name"],
|
|
24
|
+
"additionalProperties": false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Codex CLI's `--output-schema` maps to this Responses shape. The internal name
|
|
32
|
+
`codex_output_schema` is not a top-level API request parameter.
|
|
33
|
+
|
|
34
|
+
Strict function tools put the schema in `parameters`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"type": "function",
|
|
39
|
+
"name": "project_lookup",
|
|
40
|
+
"description": "Look up one project.",
|
|
41
|
+
"strict": true,
|
|
42
|
+
"parameters": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"projectId": { "type": "string" },
|
|
46
|
+
"includeArchived": { "type": ["boolean", "null"] }
|
|
47
|
+
},
|
|
48
|
+
"required": ["projectId", "includeArchived"],
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The transport forwards both shapes; it does not turn an invalid application
|
|
55
|
+
schema into a valid one.
|
|
56
|
+
|
|
57
|
+
## Required subset rules
|
|
58
|
+
|
|
59
|
+
1. The root is always `{ "type": "object" }`. Root `anyOf` and root `oneOf`
|
|
60
|
+
unions are rejected.
|
|
61
|
+
2. Every object has `additionalProperties: false`.
|
|
62
|
+
3. Every property is named in `required`. Represent model-level optionality by
|
|
63
|
+
allowing `null`, then remove a `null` placeholder before app validation when
|
|
64
|
+
the original field was optional.
|
|
65
|
+
4. Nested `anyOf` is supported. Strict function parameters reject `oneOf`;
|
|
66
|
+
avoid it or deliberately normalize it to nested `anyOf` while keeping the
|
|
67
|
+
original validator authoritative.
|
|
68
|
+
5. Do not send unsupported composition or conditionals such as `allOf`, `not`,
|
|
69
|
+
`if`, `then`, or `else` unless current official compatibility explicitly
|
|
70
|
+
supports them.
|
|
71
|
+
6. Each schema branch must constrain a real JSON type or supported union. An
|
|
72
|
+
empty schema from `z.unknown()` is not a strict schema.
|
|
73
|
+
7. Open-ended records using `propertyNames` or schema-valued
|
|
74
|
+
`additionalProperties` are outside the safe strict subset. Prefer explicit
|
|
75
|
+
known keys.
|
|
76
|
+
|
|
77
|
+
## Unions and optional fields
|
|
78
|
+
|
|
79
|
+
A Zod discriminated union commonly emits a root `oneOf`; a plain union commonly
|
|
80
|
+
emits root `anyOf`. Neither can be sent unchanged as function parameters.
|
|
81
|
+
Redesign it as one root object whose branch-specific fields are nullable:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"kind": { "type": "string", "enum": ["text", "count"] },
|
|
88
|
+
"text": { "type": ["string", "null"] },
|
|
89
|
+
"count": { "type": ["number", "null"] }
|
|
90
|
+
},
|
|
91
|
+
"required": ["kind", "text", "count"],
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
After generation, select the original branch from its discriminator, remove
|
|
97
|
+
only the nullable padding for fields optional in that branch, and validate with
|
|
98
|
+
the original Zod schema. Restoration must recurse through the selected branch;
|
|
99
|
+
walking the union wrapper alone loses its property rules.
|
|
100
|
+
|
|
101
|
+
For an optional `const`, use a real union:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "anyOf": [{ "type": "string", "const": "fixed" }, { "type": "null" }] }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Adding `null` to a separate enum does not make a non-null `const` reachable.
|
|
108
|
+
|
|
109
|
+
## Open records and arbitrary JSON
|
|
110
|
+
|
|
111
|
+
When the application truly needs an open record or `unknown` value, use an
|
|
112
|
+
explicit reversible wire representation rather than pretending it is strict:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "A JSON object encoded as a string."
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Parse the generated string as JSON, require the intended value kind, then run
|
|
122
|
+
the original application validator before execution. For arbitrary JSON, say
|
|
123
|
+
"A JSON value encoded as a string" and accept any successfully parsed JSON
|
|
124
|
+
kind. A parse failure remains invalid input; never execute the unparsed string
|
|
125
|
+
as though it satisfied the original schema.
|
|
126
|
+
|
|
127
|
+
Wire normalization may broaden what the model can generate, but it never
|
|
128
|
+
broadens what the action may execute. Application validation is the security
|
|
129
|
+
and correctness boundary.
|
|
130
|
+
|
|
131
|
+
## Failure handling
|
|
132
|
+
|
|
133
|
+
An upstream 400 with code `invalid_function_parameters` and a parameter path
|
|
134
|
+
under `tools[n].parameters` identifies a caller-owned tool schema. Return the
|
|
135
|
+
safe app error `invalid_function_parameters`, do not retain the private schema
|
|
136
|
+
detail, and do not transition subscription health to
|
|
137
|
+
`protocol_update_required`.
|
|
138
|
+
|
|
139
|
+
An upstream response-format rejection with code `invalid_json_schema` is also
|
|
140
|
+
caller-owned. It produces the bounded fingerprint
|
|
141
|
+
`protocol:codex_400_invalid_json_schema` with no subscription-health change.
|
|
142
|
+
Other parseable machine codes or types use the same sanitized, 32-character
|
|
143
|
+
`protocol:codex_400_<token>` form. Upstream messages and parameter paths are
|
|
144
|
+
not exposed. If the `400` body is malformed or contains no usable machine
|
|
145
|
+
code/type, the transport cannot assign ownership and retains the conservative
|
|
146
|
+
`protocol:unexpected_http_status_400` protocol-health fallback.
|
|
147
|
+
|
|
148
|
+
## Official provenance
|
|
149
|
+
|
|
150
|
+
Rules reconfirmed 2026-08-10:
|
|
151
|
+
|
|
152
|
+
- https://developers.openai.com/api/docs/guides/structured-outputs
|
|
153
|
+
- https://developers.openai.com/api/docs/guides/function-calling
|
|
154
|
+
- https://developers.openai.com/cookbook/examples/codex/build_code_review_with_codex_sdk#codex-structured-outputs
|
|
155
|
+
- https://github.com/openai/codex (reviewed tag and commit are recorded in
|
|
156
|
+
`module-contract.md`)
|
|
157
|
+
|
|
158
|
+
External links are provenance and update checks; the working rules are included
|
|
159
|
+
above so the installed skill remains usable on its own.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: raybase-openai-subscription
|
|
3
|
+
description: Install, configure, debug, test, or change Raybase's experimental ChatGPT/Codex subscription inference module, local relay, transport, health handling, or strict tool and response schemas.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Raybase OpenAI Subscription
|
|
7
|
+
|
|
8
|
+
Use this skill only for the explicitly selected `@raybase/openai-subscription`
|
|
9
|
+
module. It is developer guidance for an experimental integration, not a public
|
|
10
|
+
or stable OpenAI subscription API.
|
|
11
|
+
|
|
12
|
+
Read the reference that matches the task:
|
|
13
|
+
|
|
14
|
+
- [Module contract](references/module-contract.md): installed and runtime
|
|
15
|
+
integration, credential scope, relay/egress, secrets, health, and safe
|
|
16
|
+
operator guidance.
|
|
17
|
+
- [Structured outputs](references/structured-outputs.md): self-contained
|
|
18
|
+
Responses `text.format` and strict function-tool JSON Schema rules, examples,
|
|
19
|
+
Zod pitfalls, and reversible app adapters.
|
|
20
|
+
- [Compatibility and testing](references/compatibility-and-testing.md): a
|
|
21
|
+
secret-safe diagnostic ladder, error ownership, fixtures, and live smoke
|
|
22
|
+
criteria.
|
|
23
|
+
|
|
24
|
+
The module supports two declaration shapes:
|
|
25
|
+
|
|
26
|
+
- Installed: run `raybase modules add openai-subscription`. Omitted
|
|
27
|
+
`integration` means `"installed"`; preserve the generated provider/config,
|
|
28
|
+
credential-mode, background strategy, manifest, and egress contract.
|
|
29
|
+
- Runtime-registered: register the package through its public runtime API and
|
|
30
|
+
declare `{"enabled":true,"package":"@raybase/openai-subscription","integration":"runtime"}`.
|
|
31
|
+
Keep the package in production `dependencies`; declare
|
|
32
|
+
`RAYBASE_OPENAI_SUBSCRIPTION_KEY` in applicable Wrangler
|
|
33
|
+
`secrets.required` scopes and both local relay names in
|
|
34
|
+
`env.local-dev.secrets.required`. Do not adopt installed-only provider or
|
|
35
|
+
manifest checks.
|
|
36
|
+
|
|
37
|
+
Both enabled shapes start the dev-only Codex relay. Keep encryption keys and
|
|
38
|
+
all credential material exclusively in local/deployed Worker secrets. Never
|
|
39
|
+
put access tokens, refresh tokens, device secrets, identity tokens, relay
|
|
40
|
+
secrets, encryption keys, or account identifiers in source, logs, evidence,
|
|
41
|
+
browser storage, backups, fixtures, or handoff text.
|
|
42
|
+
|
|
43
|
+
Never silently fall back to Workers AI, OpenRouter, API-billed OpenAI, or
|
|
44
|
+
another chat provider. Embeddings may continue through their explicitly
|
|
45
|
+
configured path.
|
|
46
|
+
|
|
47
|
+
Preserve this non-blocking doctor information line exactly:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
info openai-subscription-usage-scope: Confirm that the selected credential scope matches the permitted use of the linked ChatGPT/Codex plan. Personal plans should not be pooled across unrelated users or tenants. For teams, use individually linked accounts or an appropriate Business, Enterprise, or API arrangement. Raybase informs but does not certify or enforce compliance.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Do not infer compliance from app shape, user count, tenancy, routes, prose, or
|
|
54
|
+
source text. If health is genuinely `protocol_update_required`, tell the
|
|
55
|
+
operator to update to a newly published reviewed module version; never suggest
|
|
56
|
+
editing fixed endpoints or allowlists. An app-owned invalid tool or response
|
|
57
|
+
schema is not subscription protocol drift and must not poison module health.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Compatibility and testing
|
|
2
|
+
|
|
3
|
+
Use this ladder to diagnose the narrowest failing contract without leaking
|
|
4
|
+
credentials or poisoning a healthy subscription lane.
|
|
5
|
+
|
|
6
|
+
## Diagnostic ladder
|
|
7
|
+
|
|
8
|
+
1. Start with a freshly linked eligible account and a minimal request with no
|
|
9
|
+
tools or response format. Confirm account/workspace/model routing and SSE.
|
|
10
|
+
2. Add one known-valid strict function tool whose parameters are a closed root
|
|
11
|
+
object. This isolates tool transport from the app's tool catalog.
|
|
12
|
+
3. Test final structured output separately with Responses `text.format`.
|
|
13
|
+
Function `parameters` and final response schemas share the strict subset but
|
|
14
|
+
occupy different request fields.
|
|
15
|
+
4. Replay the exact failing app schema. Record only HTTP status, the module's
|
|
16
|
+
sanitized and bounded fingerprint, request ID presence, counts, and sizes.
|
|
17
|
+
A bounded request ID may remain on the thrown error as an opaque support
|
|
18
|
+
correlation handle, but do not persist it automatically. Never record
|
|
19
|
+
request authorization, relay secrets, cookies, account IDs, raw messages or
|
|
20
|
+
parameter paths, bodies containing private input, or unrelated response
|
|
21
|
+
headers.
|
|
22
|
+
5. Classify ownership before changing health:
|
|
23
|
+
- auth/revocation/eligibility/account selection;
|
|
24
|
+
- rate limit or transient upstream failure;
|
|
25
|
+
- fixed transport envelope/header/stream drift;
|
|
26
|
+
- caller tool or response schema;
|
|
27
|
+
- app action validation after a valid model response.
|
|
28
|
+
6. For a parseable caller schema `400`, assert the public fingerprint is
|
|
29
|
+
sanitized and capped, the upstream message/parameter are absent, and
|
|
30
|
+
subscription health stays at its previous connected state. The app may
|
|
31
|
+
separately record its selected model attempt as failed.
|
|
32
|
+
7. After focused fixtures pass, complete a real subscription-backed app turn
|
|
33
|
+
through the local relay with the app's full tool catalog. Check the rendered
|
|
34
|
+
response and both health surfaces.
|
|
35
|
+
|
|
36
|
+
## Minimum fixtures
|
|
37
|
+
|
|
38
|
+
Cover all of these with exact schema assertions and restoration tests:
|
|
39
|
+
|
|
40
|
+
- closed root object with every property required;
|
|
41
|
+
- optional scalar represented with `null` and removed for the original app
|
|
42
|
+
validator;
|
|
43
|
+
- optional `const` using `anyOf` with `null`;
|
|
44
|
+
- root discriminated and plain unions normalized to one object;
|
|
45
|
+
- nested `oneOf` policy and nested `anyOf` output;
|
|
46
|
+
- selected-branch recursive restoration;
|
|
47
|
+
- arrays containing normalized objects;
|
|
48
|
+
- open records encoded as JSON strings and parsed back to objects;
|
|
49
|
+
- unconstrained values encoded as JSON strings and parsed back to every JSON
|
|
50
|
+
value kind;
|
|
51
|
+
- malformed/non-object roots rejected before transport;
|
|
52
|
+
- invalid JSON encodings rejected by the original validator;
|
|
53
|
+
- sanitized JSON refusal fixtures for both response-format
|
|
54
|
+
`invalid_json_schema` and tool `invalid_function_parameters` envelopes;
|
|
55
|
+
- `invalid_function_parameters` mapped without upstream detail or a health
|
|
56
|
+
incident;
|
|
57
|
+
- code-first/type-fallback normalization, hostile punctuation, a 32-character
|
|
58
|
+
token cap, and omission of overlong request IDs;
|
|
59
|
+
- empty, malformed, oversized, non-object, and detail-free `400` bodies mapped
|
|
60
|
+
to `protocol:unexpected_http_status_400` with
|
|
61
|
+
`protocol_update_required`;
|
|
62
|
+
- provider-level proof that a connected subscription remains connected with no
|
|
63
|
+
incident for a parseable request rejection, while an unclassifiable `400`
|
|
64
|
+
still transitions health.
|
|
65
|
+
|
|
66
|
+
## Request content verified 2026-08-11
|
|
67
|
+
|
|
68
|
+
A redacted local probe against the private subscription endpoint verified the
|
|
69
|
+
request-side image boundary independently of the public OpenAI API:
|
|
70
|
+
|
|
71
|
+
- Ordered image content uses `{ "type": "input_image", "image_url": ... }`.
|
|
72
|
+
Both an absolute HTTPS URL and a `data:image/...;base64,...` value completed.
|
|
73
|
+
- Inline PNG, JPEG, WEBP, and non-animated GIF samples completed.
|
|
74
|
+
- Omitting `detail` completed, as did explicit upstream probes for `auto`,
|
|
75
|
+
`low`, `high`, and `original`. Raybase's provider-neutral image contract does
|
|
76
|
+
not expose that knob, so the adapter always omits it; no semantic equivalence
|
|
77
|
+
between the values was inferred.
|
|
78
|
+
- `quality` is not an image-input field here. The endpoint rejected it with
|
|
79
|
+
`400 unknown_parameter` at the image part, and Raybase rejects it locally
|
|
80
|
+
rather than sending it.
|
|
81
|
+
- A valid 1x1 PNG carrying valid ancillary padding completed through
|
|
82
|
+
**19,573,864 encoded JSON request bytes** and **14,680,144 decoded image
|
|
83
|
+
bytes**. No rejection occurred below the probe's hard 20 MiB request-body
|
|
84
|
+
safety cap, so the exact private limit remains unverified. This proves a
|
|
85
|
+
request-body lower bound, not an image-decoding or useful-pixel bound.
|
|
86
|
+
|
|
87
|
+
Agent attachments remain deliberately more conservative: the default is 8 MiB
|
|
88
|
+
per file, before base64 expansion, plus the configured per-message attachment
|
|
89
|
+
count. Endpoint permissiveness does not widen those runtime admission limits.
|
|
90
|
+
|
|
91
|
+
Documented or adjacent content kinds that remain unsupported are Responses
|
|
92
|
+
`input_file` (file URL, file ID, or base64 file data), image-by-file-ID, Codex
|
|
93
|
+
`input_audio`/audio URLs, multimodal function-call outputs, assistant image
|
|
94
|
+
content, and image-generation/output-item families. Core exposes no image
|
|
95
|
+
result or stream event and this module does not author an image-generation
|
|
96
|
+
tool. The response-side test therefore asserts the gap precisely:
|
|
97
|
+
`image_generation_call` still fails closed as
|
|
98
|
+
`protocol:unknown_output_item`; it is not silently skipped.
|
|
99
|
+
|
|
100
|
+
## Live smoke requirements
|
|
101
|
+
|
|
102
|
+
Use a disposable app copy and the candidate Core CLI/package, not an old
|
|
103
|
+
workspace binary. Keep existing secret files out of output and source control.
|
|
104
|
+
For a runtime integration:
|
|
105
|
+
|
|
106
|
+
1. `raybase modules doctor` passes the runtime contract.
|
|
107
|
+
2. `raybase doctor`/`raybase check` contain no installed-only subscription
|
|
108
|
+
findings.
|
|
109
|
+
3. `raybase modules status --json` reports `integration: "runtime"`, enabled,
|
|
110
|
+
not scaffold-installed.
|
|
111
|
+
4. `raybase dev` starts the loopback relay and injects both local names.
|
|
112
|
+
5. A linked subscription turn reaches the expected model with the full app tool
|
|
113
|
+
set and renders a response.
|
|
114
|
+
6. Subscription health and app model health are healthy afterward.
|
|
115
|
+
7. Clean shutdown removes temporary relay values.
|
|
116
|
+
8. Removing the declaration restores the non-blocking undeclared-dependency
|
|
117
|
+
advisory and skips relay startup.
|
|
118
|
+
|
|
119
|
+
Do not replace the real linked turn with a mock when live acceptance is part of
|
|
120
|
+
the plan. If linking or upstream access is unavailable, record the exact blocker
|
|
121
|
+
and keep the implementation partial.
|
|
122
|
+
|
|
123
|
+
## Reconfirming Codex mapping
|
|
124
|
+
|
|
125
|
+
When Codex CLI behavior matters, inspect a pinned open-source tag. Trace
|
|
126
|
+
`--output-schema` from CLI configuration to the Responses request and compare it
|
|
127
|
+
with current official OpenAI docs. Do not infer an API parameter from an
|
|
128
|
+
internal Rust type name, and do not vendor a mutable Codex checkout into the
|
|
129
|
+
module skill.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Module contract
|
|
2
|
+
|
|
3
|
+
This reference describes the working developer contract for Raybase's
|
|
4
|
+
experimental `@raybase/openai-subscription` module. Private upstream details
|
|
5
|
+
are observations, not a public OpenAI API guarantee.
|
|
6
|
+
|
|
7
|
+
## Integration shapes
|
|
8
|
+
|
|
9
|
+
### Installed
|
|
10
|
+
|
|
11
|
+
`raybase modules add openai-subscription` owns the scaffold, module lock,
|
|
12
|
+
selected `openai-codex-subscription` provider, credential and background
|
|
13
|
+
strategy, generated provider manifest, and production egress binding. An
|
|
14
|
+
omitted `integration` field means `"installed"`. Exact install-lock provenance
|
|
15
|
+
wins; it cannot be relabeled runtime to bypass drift checks.
|
|
16
|
+
|
|
17
|
+
### Runtime-registered
|
|
18
|
+
|
|
19
|
+
An app with its own dispatcher may register the package's public runtime API
|
|
20
|
+
and declare:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"raybase": {
|
|
25
|
+
"modules": {
|
|
26
|
+
"openai-subscription": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"package": "@raybase/openai-subscription",
|
|
29
|
+
"integration": "runtime"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The exact package must be a production dependency. Applicable Wrangler
|
|
37
|
+
`secrets.required` scopes declare `RAYBASE_OPENAI_SUBSCRIPTION_KEY`, and
|
|
38
|
+
`env.local-dev.secrets.required` declares `RAYBASE_LOCAL_CODEX_RELAY_URL` and
|
|
39
|
+
`RAYBASE_LOCAL_CODEX_RELAY_SECRET`. Runtime doctor does not require the
|
|
40
|
+
installed provider source, selected-provider manifest, or `CODEX_EGRESS`.
|
|
41
|
+
|
|
42
|
+
An enabled declaration of either flavor starts the dev relay. An undeclared
|
|
43
|
+
direct dependency does not; doctor gives a non-blocking advisory rather than
|
|
44
|
+
guessing from imports.
|
|
45
|
+
|
|
46
|
+
## Credentials and scope
|
|
47
|
+
|
|
48
|
+
- `operator`: one explicitly responsible operator-linked credential scope.
|
|
49
|
+
- `user`: separately linked user scopes; never cross users or tenants.
|
|
50
|
+
- `organization`: use only with the corresponding reviewed organization
|
|
51
|
+
arrangement and application policy.
|
|
52
|
+
|
|
53
|
+
Store encrypted credentials in the module's durable store. Keep the encryption
|
|
54
|
+
key in Worker secrets only. Device codes may be shown during an active link,
|
|
55
|
+
but access, refresh, identity, device, relay, and encryption secrets never enter
|
|
56
|
+
logs, source, browser persistence, fixtures, backups, screenshots, or handoffs.
|
|
57
|
+
Account/workspace selection is explicit; relinking replaces the intended scope,
|
|
58
|
+
not an arbitrary healthy credential.
|
|
59
|
+
|
|
60
|
+
Raybase informs but does not certify subscription-plan compliance. Never pool a
|
|
61
|
+
personal plan across unrelated employees, customers, users, or tenants. Prefer
|
|
62
|
+
individually linked eligible accounts or the appropriate Business, Enterprise,
|
|
63
|
+
or API arrangement.
|
|
64
|
+
|
|
65
|
+
These illustrations remain explicitly non-certifying:
|
|
66
|
+
|
|
67
|
+
- Plausible personal-use example: a private, noncommercial household app
|
|
68
|
+
operated by the subscription holder and used within that household—
|
|
69
|
+
Birch-shaped, with one household, one operator, two partners, and one
|
|
70
|
+
subscription in the operator's name.
|
|
71
|
+
- Likely inappropriate: one personal subscription pooled for employees,
|
|
72
|
+
customers, unrelated users, or multiple tenants.
|
|
73
|
+
- Safer small-team pattern: each user links an eligible subscription, or the
|
|
74
|
+
team uses an appropriate Business, Enterprise, or API arrangement.
|
|
75
|
+
- Multi-tenant pattern: tenant/user-scoped credentials; never silently reuse
|
|
76
|
+
one tenant or operator credential in another tenant.
|
|
77
|
+
|
|
78
|
+
## Local relay and deployed egress
|
|
79
|
+
|
|
80
|
+
Local development uses a per-boot, loopback-only relay. `raybase dev` injects a
|
|
81
|
+
URL and authorization secret into the local worker for that session and removes
|
|
82
|
+
them during cleanup. Do not print or persist the secret. A relay startup or
|
|
83
|
+
injection failure is fatal to dev startup and must name the consequence: the
|
|
84
|
+
OpenAI subscription lane is unavailable for the session.
|
|
85
|
+
|
|
86
|
+
Production uses the reviewed egress binding and fixed transport implementation.
|
|
87
|
+
Keep the route allowlist narrow, strip caller-supplied authorization and routing
|
|
88
|
+
headers, set only reviewed headers, stream without buffering the whole response,
|
|
89
|
+
propagate cancellation, and bound error-body reads. Do not make endpoints,
|
|
90
|
+
headers, or account-routing details application-configurable.
|
|
91
|
+
|
|
92
|
+
## Health and error ownership
|
|
93
|
+
|
|
94
|
+
Persisted subscription health describes the credential/transport lane, not all
|
|
95
|
+
caller mistakes.
|
|
96
|
+
|
|
97
|
+
- Authentication, account eligibility, workspace selection, revocation, rate
|
|
98
|
+
limits, and reviewed transport compatibility map to their specific safe
|
|
99
|
+
health states and retry/action guidance.
|
|
100
|
+
- `protocol_update_required` is reserved for transport-owned drift: a fixed
|
|
101
|
+
endpoint, required header, request/response envelope, or streaming protocol
|
|
102
|
+
no longer matches the reviewed implementation.
|
|
103
|
+
- A parseable Codex `400` with a usable machine `error.code`, or `error.type`
|
|
104
|
+
as fallback, is a safe request error. Normalize it to lowercase, replace
|
|
105
|
+
non-`[a-z0-9_]` runs, trim separators, cap the token at 32 characters, and
|
|
106
|
+
emit `protocol:codex_400_<token>` without a health field.
|
|
107
|
+
- Preserve the exact `invalid_function_parameters` plus
|
|
108
|
+
`tools[n].parameters` ownership case. Free-form upstream `message` text
|
|
109
|
+
never participates in classification or public output; `param` participates
|
|
110
|
+
only in that exact tool-schema check. Leave previously connected
|
|
111
|
+
subscription health intact.
|
|
112
|
+
- Empty, malformed, oversized, non-object, and machine-detail-free `400`
|
|
113
|
+
bodies remain `protocol:unexpected_http_status_400` with
|
|
114
|
+
`protocol_update_required` because caller ownership is not established.
|
|
115
|
+
- A trimmed, non-empty `x-oai-request-id` of at most 128 characters may travel
|
|
116
|
+
as opaque `oaiRequestId` metadata on the thrown error. Keep it separate from
|
|
117
|
+
the fingerprint and do not automatically persist it in health, incidents,
|
|
118
|
+
notifications, or audits.
|
|
119
|
+
- App model routing may separately mark its chosen model lane unhealthy after a
|
|
120
|
+
bad request; that must not be confused with subscription credential health.
|
|
121
|
+
- Transient upstream failures use bounded retry policy and must not be relabeled
|
|
122
|
+
permanent protocol drift.
|
|
123
|
+
|
|
124
|
+
When genuine protocol drift occurs, update and re-review the module. Operators
|
|
125
|
+
must not edit fixed endpoints, allowlists, or private headers to work around it.
|
|
126
|
+
|
|
127
|
+
## Provenance
|
|
128
|
+
|
|
129
|
+
Observed and reconfirmed 2026-08-10 against the open-source Codex CLI tag
|
|
130
|
+
`rust-v0.146.0-alpha.9.2` (commit
|
|
131
|
+
`86cc9f2177cad015befd595286d8767a650f7d13`) and live Responses behavior. Recheck
|
|
132
|
+
these observations during compatibility updates; do not treat the date or tag
|
|
133
|
+
as an upstream stability promise.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Structured outputs and strict tool schemas
|
|
2
|
+
|
|
3
|
+
This is the self-contained schema contract developers need when sending
|
|
4
|
+
structured output through the module. It is the standard OpenAI Structured
|
|
5
|
+
Outputs subset, not a subscription-specific alternative.
|
|
6
|
+
|
|
7
|
+
## Two request shapes, one strict subset
|
|
8
|
+
|
|
9
|
+
Final structured output in the Responses API uses `text.format`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"model": "MODEL_ID",
|
|
14
|
+
"input": "Extract project metadata",
|
|
15
|
+
"text": {
|
|
16
|
+
"format": {
|
|
17
|
+
"type": "json_schema",
|
|
18
|
+
"name": "project_metadata",
|
|
19
|
+
"strict": true,
|
|
20
|
+
"schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": { "name": { "type": "string" } },
|
|
23
|
+
"required": ["name"],
|
|
24
|
+
"additionalProperties": false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Codex CLI's `--output-schema` maps to this Responses shape. The internal name
|
|
32
|
+
`codex_output_schema` is not a top-level API request parameter.
|
|
33
|
+
|
|
34
|
+
Strict function tools put the schema in `parameters`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"type": "function",
|
|
39
|
+
"name": "project_lookup",
|
|
40
|
+
"description": "Look up one project.",
|
|
41
|
+
"strict": true,
|
|
42
|
+
"parameters": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"projectId": { "type": "string" },
|
|
46
|
+
"includeArchived": { "type": ["boolean", "null"] }
|
|
47
|
+
},
|
|
48
|
+
"required": ["projectId", "includeArchived"],
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The transport forwards both shapes; it does not turn an invalid application
|
|
55
|
+
schema into a valid one.
|
|
56
|
+
|
|
57
|
+
## Required subset rules
|
|
58
|
+
|
|
59
|
+
1. The root is always `{ "type": "object" }`. Root `anyOf` and root `oneOf`
|
|
60
|
+
unions are rejected.
|
|
61
|
+
2. Every object has `additionalProperties: false`.
|
|
62
|
+
3. Every property is named in `required`. Represent model-level optionality by
|
|
63
|
+
allowing `null`, then remove a `null` placeholder before app validation when
|
|
64
|
+
the original field was optional.
|
|
65
|
+
4. Nested `anyOf` is supported. Strict function parameters reject `oneOf`;
|
|
66
|
+
avoid it or deliberately normalize it to nested `anyOf` while keeping the
|
|
67
|
+
original validator authoritative.
|
|
68
|
+
5. Do not send unsupported composition or conditionals such as `allOf`, `not`,
|
|
69
|
+
`if`, `then`, or `else` unless current official compatibility explicitly
|
|
70
|
+
supports them.
|
|
71
|
+
6. Each schema branch must constrain a real JSON type or supported union. An
|
|
72
|
+
empty schema from `z.unknown()` is not a strict schema.
|
|
73
|
+
7. Open-ended records using `propertyNames` or schema-valued
|
|
74
|
+
`additionalProperties` are outside the safe strict subset. Prefer explicit
|
|
75
|
+
known keys.
|
|
76
|
+
|
|
77
|
+
## Unions and optional fields
|
|
78
|
+
|
|
79
|
+
A Zod discriminated union commonly emits a root `oneOf`; a plain union commonly
|
|
80
|
+
emits root `anyOf`. Neither can be sent unchanged as function parameters.
|
|
81
|
+
Redesign it as one root object whose branch-specific fields are nullable:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"kind": { "type": "string", "enum": ["text", "count"] },
|
|
88
|
+
"text": { "type": ["string", "null"] },
|
|
89
|
+
"count": { "type": ["number", "null"] }
|
|
90
|
+
},
|
|
91
|
+
"required": ["kind", "text", "count"],
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
After generation, select the original branch from its discriminator, remove
|
|
97
|
+
only the nullable padding for fields optional in that branch, and validate with
|
|
98
|
+
the original Zod schema. Restoration must recurse through the selected branch;
|
|
99
|
+
walking the union wrapper alone loses its property rules.
|
|
100
|
+
|
|
101
|
+
For an optional `const`, use a real union:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "anyOf": [{ "type": "string", "const": "fixed" }, { "type": "null" }] }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Adding `null` to a separate enum does not make a non-null `const` reachable.
|
|
108
|
+
|
|
109
|
+
## Open records and arbitrary JSON
|
|
110
|
+
|
|
111
|
+
When the application truly needs an open record or `unknown` value, use an
|
|
112
|
+
explicit reversible wire representation rather than pretending it is strict:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "A JSON object encoded as a string."
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Parse the generated string as JSON, require the intended value kind, then run
|
|
122
|
+
the original application validator before execution. For arbitrary JSON, say
|
|
123
|
+
"A JSON value encoded as a string" and accept any successfully parsed JSON
|
|
124
|
+
kind. A parse failure remains invalid input; never execute the unparsed string
|
|
125
|
+
as though it satisfied the original schema.
|
|
126
|
+
|
|
127
|
+
Wire normalization may broaden what the model can generate, but it never
|
|
128
|
+
broadens what the action may execute. Application validation is the security
|
|
129
|
+
and correctness boundary.
|
|
130
|
+
|
|
131
|
+
## Failure handling
|
|
132
|
+
|
|
133
|
+
An upstream 400 with code `invalid_function_parameters` and a parameter path
|
|
134
|
+
under `tools[n].parameters` identifies a caller-owned tool schema. Return the
|
|
135
|
+
safe app error `invalid_function_parameters`, do not retain the private schema
|
|
136
|
+
detail, and do not transition subscription health to
|
|
137
|
+
`protocol_update_required`.
|
|
138
|
+
|
|
139
|
+
An upstream response-format rejection with code `invalid_json_schema` is also
|
|
140
|
+
caller-owned. It produces the bounded fingerprint
|
|
141
|
+
`protocol:codex_400_invalid_json_schema` with no subscription-health change.
|
|
142
|
+
Other parseable machine codes or types use the same sanitized, 32-character
|
|
143
|
+
`protocol:codex_400_<token>` form. Upstream messages and parameter paths are
|
|
144
|
+
not exposed. If the `400` body is malformed or contains no usable machine
|
|
145
|
+
code/type, the transport cannot assign ownership and retains the conservative
|
|
146
|
+
`protocol:unexpected_http_status_400` protocol-health fallback.
|
|
147
|
+
|
|
148
|
+
## Official provenance
|
|
149
|
+
|
|
150
|
+
Rules reconfirmed 2026-08-10:
|
|
151
|
+
|
|
152
|
+
- https://developers.openai.com/api/docs/guides/structured-outputs
|
|
153
|
+
- https://developers.openai.com/api/docs/guides/function-calling
|
|
154
|
+
- https://developers.openai.com/cookbook/examples/codex/build_code_review_with_codex_sdk#codex-structured-outputs
|
|
155
|
+
- https://github.com/openai/codex (reviewed tag and commit are recorded in
|
|
156
|
+
`module-contract.md`)
|
|
157
|
+
|
|
158
|
+
External links are provenance and update checks; the working rules are included
|
|
159
|
+
above so the installed skill remains usable on its own.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { openaiSubscriptionModule, type OpenAISubscriptionFetcher } from "@raybase/openai-subscription";
|
|
2
|
+
import type { RaybaseEnv } from "@raybase/core/worker";
|
|
3
|
+
|
|
4
|
+
export const openaiSubscription = openaiSubscriptionModule<RaybaseEnv & { CODEX_EGRESS: OpenAISubscriptionFetcher }>({
|
|
5
|
+
credentialMode: "user",
|
|
6
|
+
background: { strategy: "disabled" },
|
|
7
|
+
applicationScope: "raybase-app",
|
|
8
|
+
codexEgress: import.meta.env.DEV
|
|
9
|
+
? { strategy: "local-relay" }
|
|
10
|
+
: { strategy: "cloudflare-vpc", binding: "CODEX_EGRESS" }
|
|
11
|
+
});
|