@raybase/openai-subscription 0.0.2 → 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
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# OpenAI subscription inference decision
|
|
2
|
+
|
|
3
|
+
Status: accepted for the experimental `0.1.x` module line.
|
|
4
|
+
|
|
5
|
+
Raybase Core exposes a named, request-scoped inference-provider factory. The
|
|
6
|
+
optional `@raybase/openai-subscription` module registers exactly one provider,
|
|
7
|
+
`openai-codex-subscription`. Apps select it explicitly through
|
|
8
|
+
`appConfig.ai.provider`; duplicate or missing provider ids fail at startup.
|
|
9
|
+
Chat and streaming never fall back after selection. The provider intentionally
|
|
10
|
+
omits embeddings, so the existing explicit Workers AI embedding path remains
|
|
11
|
+
available.
|
|
12
|
+
|
|
13
|
+
The module owns the compatibility-sensitive Codex transport, hidden system
|
|
14
|
+
tables, scope resolver, encrypted credential envelopes, connection routes, and
|
|
15
|
+
health state. Core remains provider-neutral. Credential identity is the exact
|
|
16
|
+
application, organization, mode, and subject tuple, authenticated as AES-GCM
|
|
17
|
+
AAD. The existing organization-scoped Pro database is the primary tenant
|
|
18
|
+
boundary; the tuple is an additional boundary.
|
|
19
|
+
|
|
20
|
+
Production endpoints are fixed to the reviewed compatibility manifest.
|
|
21
|
+
Unknown protocol shapes fail closed as `protocol_update_required`. A bounded,
|
|
22
|
+
parseable Codex `400` with a usable machine `error.code` or `error.type` is
|
|
23
|
+
instead a caller/request rejection with a sanitized
|
|
24
|
+
`protocol:codex_400_<token>` fingerprint and no health transition. Empty,
|
|
25
|
+
malformed, oversized, non-object, or machine-detail-free `400` bodies remain
|
|
26
|
+
the conservative protocol fallback. Upstream free-form messages never cross
|
|
27
|
+
the transport boundary. Credentials are never module options, settings, or
|
|
28
|
+
manifest data. Background work is disabled unless its mode-specific execution
|
|
29
|
+
scope is explicitly configured.
|
|
30
|
+
|
|
31
|
+
This direct private-protocol adapter is isolated behind an internal transport
|
|
32
|
+
boundary so a future reviewed broker or Codex app-server adapter can implement
|
|
33
|
+
the same provider contract without changing application configuration.
|
|
34
|
+
|
|
35
|
+
Origin validation and endpoint authorization are deliberately separate.
|
|
36
|
+
Transport construction accepts only bare HTTPS origins; request dispatch then
|
|
37
|
+
checks the configured origin and exact fixed method/path immediately before
|
|
38
|
+
fetch. Arbitrary origins and fetchers exist only in the package's `./testing`
|
|
39
|
+
entry point. Ordinary module and provider construction cannot change canonical
|
|
40
|
+
destinations.
|
|
41
|
+
|
|
42
|
+
Cloudflare Worker deployments can select a request-scoped VPC Network Fetcher
|
|
43
|
+
binding for the canonical Codex responses endpoint. The binding is taken from
|
|
44
|
+
the current provider or route environment, is structurally limited to
|
|
45
|
+
`fetch()`, and is never captured as a global capability. Auth traffic remains
|
|
46
|
+
direct; Codex traffic uses the VPC binding exactly once. Missing bindings,
|
|
47
|
+
throws, redirects, declared non-SSE responses, and 403 HTML responses fail
|
|
48
|
+
closed and never fall back to global fetch. The canonical Codex endpoint may
|
|
49
|
+
omit `Content-Type`; only a missing or empty value is admitted to the same
|
|
50
|
+
bounded strict SSE parser, which must still reach a completed response. A
|
|
51
|
+
declared non-SSE response is rejected before body read. Diagnostics retain
|
|
52
|
+
status, normalized content type, retry metadata, and safe `cf-ray` correlation
|
|
53
|
+
only; bodies and credential or account data are discarded. A bounded
|
|
54
|
+
`x-oai-request-id` may travel as opaque `oaiRequestId` metadata on a thrown
|
|
55
|
+
error, independently of its fingerprint, but is not written into health,
|
|
56
|
+
incident, notification, or audit state.
|
|
57
|
+
|
|
58
|
+
Local workerd cannot reach the canonical Codex endpoint through its direct
|
|
59
|
+
client fingerprint. Under `raybase dev`, apps instead select `local-relay`.
|
|
60
|
+
The CLI owns an ephemeral Node server bound to IPv4 loopback and injects its
|
|
61
|
+
URL plus a 256-bit per-boot secret into the local Worker. The transport accepts
|
|
62
|
+
only a loopback relay URL while `RAYBASE_ENV=local`, adds the relay guard only
|
|
63
|
+
to that hop, and leaves auth traffic direct. The relay strips its guard,
|
|
64
|
+
allows only the fixed Codex responses method/path, forwards the original body
|
|
65
|
+
and request headers, uses manual redirects, and streams the response. It emits
|
|
66
|
+
no request logs. This capability is dev-only and never replaces the
|
|
67
|
+
production VPC route.
|
|
68
|
+
|
|
69
|
+
Core owns output-budget admission and accounting above this adapter. It passes
|
|
70
|
+
the effective `maxOutputTokens` through the provider-neutral request so the
|
|
71
|
+
reservation happens before dispatch, while the adapter omits the unsupported
|
|
72
|
+
`max_output_tokens` subscription field. The existing 4 MB SSE limit is the
|
|
73
|
+
remaining per-response size ceiling.
|
|
74
|
+
|
|
75
|
+
The VPC mechanism follows Cloudflare's beta Workers VPC and Gateway egress
|
|
76
|
+
documentation. Unit and mock Worker coverage cannot establish edge
|
|
77
|
+
reachability: release acceptance requires the repository-owned source-checkout
|
|
78
|
+
Worker fixture to finish a minimal `gpt-5.6-terra` SSE response through
|
|
79
|
+
`network_id: "cf1:network"`. A remaining 403 is reported as a platform blocker,
|
|
80
|
+
not treated as expired auth or an invitation to imitate a browser.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Itself & Co
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,199 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@raybase/openai-subscription`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Experimental, explicitly opt-in Raybase module for ChatGPT/Codex
|
|
4
|
+
subscription-backed Raybase Agent inference.
|
|
5
|
+
|
|
6
|
+
This is compatibility code, not the public OpenAI API and not API-key billing.
|
|
7
|
+
The Worker-native transport is pinned to the reviewed upstream Codex revision
|
|
8
|
+
recorded in `compatibility/manifest.json`. Unknown protocol shapes fail closed
|
|
9
|
+
as `protocol_update_required`.
|
|
10
|
+
|
|
11
|
+
Codex `400` responses are classified only from bounded machine fields. A
|
|
12
|
+
parseable `error.code`, or `error.type` when the code is absent or unusable,
|
|
13
|
+
becomes a lowercase, punctuation-normalized token capped at 32 characters in a
|
|
14
|
+
fingerprint such as `protocol:codex_400_invalid_json_schema`. This is a
|
|
15
|
+
health-neutral request failure: it does not change a connected subscription or
|
|
16
|
+
create a protocol incident. Free-form upstream `message` text is discarded,
|
|
17
|
+
and `param` is used only by the exact `invalid_function_parameters` tool-schema
|
|
18
|
+
case. Empty, malformed, oversized, non-object, and detail-free `400` bodies
|
|
19
|
+
remain `protocol:unexpected_http_status_400` with
|
|
20
|
+
`protocol_update_required` because ownership cannot be established safely.
|
|
21
|
+
|
|
22
|
+
A trimmed, non-empty `x-oai-request-id` of at most 128 characters is retained
|
|
23
|
+
as opaque `oaiRequestId` metadata on the thrown transport/request error. It is
|
|
24
|
+
not part of the fingerprint and is not automatically persisted or displayed
|
|
25
|
+
in subscription health, incidents, notifications, or audits.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import {
|
|
29
|
+
openaiSubscriptionModule,
|
|
30
|
+
type OpenAISubscriptionFetcher
|
|
31
|
+
} from "@raybase/openai-subscription";
|
|
32
|
+
import type { RaybaseEnv } from "@raybase/core/worker";
|
|
33
|
+
|
|
34
|
+
type Env = RaybaseEnv & { CODEX_EGRESS: OpenAISubscriptionFetcher };
|
|
35
|
+
|
|
36
|
+
openaiSubscriptionModule<Env>({
|
|
37
|
+
credentialMode: "user",
|
|
38
|
+
background: { strategy: "run-as-user" },
|
|
39
|
+
codexEgress: import.meta.env.DEV
|
|
40
|
+
? { strategy: "local-relay" }
|
|
41
|
+
: { strategy: "cloudflare-vpc", binding: "CODEX_EGRESS" }
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For a production Cloudflare Worker, add the matching Workers VPC Network
|
|
46
|
+
binding to `wrangler.jsonc`:
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{
|
|
50
|
+
"vpc_networks": [
|
|
51
|
+
{
|
|
52
|
+
"binding": "CODEX_EGRESS",
|
|
53
|
+
"network_id": "cf1:network",
|
|
54
|
+
"remote": true
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`raybase modules add openai-subscription` writes this binding to production.
|
|
61
|
+
`raybase modules doctor` reports a missing or malformed production binding.
|
|
62
|
+
|
|
63
|
+
For local development, the installed module selects `local-relay`.
|
|
64
|
+
`raybase dev` starts an ephemeral Node relay bound to `127.0.0.1`, injects a
|
|
65
|
+
per-boot URL and shared secret into the local Worker, and removes them on clean
|
|
66
|
+
shutdown. Only Codex response traffic uses that relay; device and token traffic
|
|
67
|
+
continues directly to `https://auth.openai.com`. The relay accepts only the
|
|
68
|
+
Codex responses route and never logs request header values. `local-relay`
|
|
69
|
+
fails closed outside a Worker whose `RAYBASE_ENV` is `local`.
|
|
70
|
+
|
|
71
|
+
The binding is resolved from the active request's Worker environment. Codex
|
|
72
|
+
response traffic goes only through `CODEX_EGRESS.fetch()`; device and token
|
|
73
|
+
traffic remains on canonical `https://auth.openai.com` through normal fetch.
|
|
74
|
+
A missing, malformed, throwing, or 403 VPC binding fails closed. There is no
|
|
75
|
+
retry through global fetch. Standalone Node tools may omit `codexEgress` or use
|
|
76
|
+
`{ strategy: "direct" }` for backward-compatible direct traffic.
|
|
77
|
+
|
|
78
|
+
Cloudflare documents this public-Internet Gateway route in the
|
|
79
|
+
[Workers VPC overview](https://developers.cloudflare.com/workers-vpc/),
|
|
80
|
+
[binding API](https://developers.cloudflare.com/workers-vpc/api/), and
|
|
81
|
+
[Gateway egress announcement](https://developers.cloudflare.com/changelog/post/2026-06-05-gateway-egress/).
|
|
82
|
+
Workers VPC is currently beta, so re-check those documents before deployment.
|
|
83
|
+
|
|
84
|
+
`maxOutputTokens` remains a Raybase admission and accounting input. Core
|
|
85
|
+
reserves the effective value before provider dispatch and records observed
|
|
86
|
+
usage, but the subscription wire does not send unsupported
|
|
87
|
+
`max_output_tokens`. An admitted response is bounded by the transport's 4 MB
|
|
88
|
+
SSE ceiling, not by server-side per-response token truncation.
|
|
89
|
+
|
|
90
|
+
## Image inputs
|
|
91
|
+
|
|
92
|
+
User messages may interleave text, HTTPS image URLs, and inline base64/data-URI
|
|
93
|
+
images. The subscription adapter preserves that order and emits Responses
|
|
94
|
+
`input_text` and `input_image` items; an image always uses the verified
|
|
95
|
+
`input_image.image_url` field. Supported media types are PNG, JPEG, WEBP, and
|
|
96
|
+
non-animated GIF. System, assistant, and tool message content remains
|
|
97
|
+
string-only.
|
|
98
|
+
|
|
99
|
+
This boundary is intentionally strict. Unsupported kinds such as audio or
|
|
100
|
+
files, malformed MIME/base64/URL values, and extra fields are rejected before
|
|
101
|
+
fetch with caller errors that name the kind or field. In particular, Raybase
|
|
102
|
+
does not expose per-image `detail` or `quality`: it omits both and uses the
|
|
103
|
+
endpoint's accepted omitted-field behavior. See the dated evidence and exact
|
|
104
|
+
unsupported-kind inventory in
|
|
105
|
+
`skills/raybase-openai-subscription/references/compatibility-and-testing.md`.
|
|
106
|
+
|
|
107
|
+
## Live Worker verification
|
|
108
|
+
|
|
109
|
+
Mock Fetcher tests prove routing and fail-closed behavior, not ChatGPT
|
|
110
|
+
reachability. The fixture is a Raybase repository source-checkout acceptance
|
|
111
|
+
gate under `packages/openai-subscription/test-worker/`; it is intentionally
|
|
112
|
+
not included in the published package and published consumers receive no
|
|
113
|
+
fixture scripts. From the repository root:
|
|
114
|
+
|
|
115
|
+
1. Run `pnpm deploy:openai-subscription-worker-egress:remote` for the narrowly
|
|
116
|
+
named ephemeral Worker in
|
|
117
|
+
`packages/openai-subscription/test-worker/wrangler.jsonc`.
|
|
118
|
+
2. Run `pnpm exec wrangler secret put OPENAI_SUBSCRIPTION_TOKEN --config packages/openai-subscription/test-worker/wrangler.jsonc`
|
|
119
|
+
and the same command for `OPENAI_SUBSCRIPTION_VERIFICATION_KEY`. Let
|
|
120
|
+
Wrangler prompt on stdin; never
|
|
121
|
+
put either value in configuration, source, command arguments, logs, or
|
|
122
|
+
output.
|
|
123
|
+
3. POST `/verify` with the one-time key in the Authorization header. A pass
|
|
124
|
+
reports only route, model, status, the validated SSE protocol, and final
|
|
125
|
+
token counts. The canonical endpoint can omit `Content-Type`; a missing or
|
|
126
|
+
empty value is accepted only when the bounded strict SSE parser reaches a
|
|
127
|
+
completed response. A declared non-SSE media type fails before body read
|
|
128
|
+
and reports only normalized content type, `cf-ray` presence, and redirect
|
|
129
|
+
state; the upstream body remains unread and is cancelled.
|
|
130
|
+
4. Immediately run `pnpm delete:openai-subscription-worker-egress:remote`. A 403
|
|
131
|
+
is a platform blocker; do not add browser headers or a direct-fetch retry.
|
|
132
|
+
5. Toby must rotate the furnished development subscription token after the
|
|
133
|
+
verification evidence is captured.
|
|
134
|
+
|
|
135
|
+
### The `cloudflare-vpc` lane, verified 2026-08-10
|
|
136
|
+
|
|
137
|
+
Acceptance item **met**: one real turn through `CODEX_EGRESS` from a genuinely
|
|
138
|
+
deployed Worker (`wrangler deploy`, binding `cf1:network`), carrying a
|
|
139
|
+
credential minted by this module's own device-code flow, returned inner status
|
|
140
|
+
`200`, validated SSE, `gpt-5.6-terra`, usage 11 in / 5 out. The spike ran from
|
|
141
|
+
committed `75dfca7` — that is, **without** the outbound header allowlist below
|
|
142
|
+
— which is what makes it decisive: neither the Workers runtime nor Cloudflare's
|
|
143
|
+
VPC egress decorates those subrequests with the headers that block the front
|
|
144
|
+
door. Production device-auth traffic was never affected.
|
|
145
|
+
|
|
146
|
+
chatgpt.com sits behind Cloudflare, and its front door blocks any request
|
|
147
|
+
carrying Cloudflare's own hop metadata. `cf-worker` — which workerd stamps on
|
|
148
|
+
every outbound subrequest — plus `cf-connecting-ip` and `cf-visitor` each
|
|
149
|
+
return `403 text/html` with the blocked page and **no `x-oai-request-id`**,
|
|
150
|
+
because the request never reaches the Codex API. (`cf-ray` and `cf-ipcountry`
|
|
151
|
+
do not trigger it.) This, not any property of the credential, is what blocked
|
|
152
|
+
the local lane through August 2026: `raybase dev`'s relay moved the TCP client
|
|
153
|
+
to Node but forwarded workerd's `cf-worker` header unchanged.
|
|
154
|
+
|
|
155
|
+
Two boundaries now guard this: the relay strips that header class at its own
|
|
156
|
+
edge, and the transport authors its outbound header set from an allowlist
|
|
157
|
+
(`content-type`, `authorization`, `chatgpt-account-id`, `originator`,
|
|
158
|
+
`accept`) for every egress strategy.
|
|
159
|
+
|
|
160
|
+
The allowlist is a guard, not a repair: the module has never authored a `cf-*`
|
|
161
|
+
header on any strategy, and the deploy above proves the runtime does not add
|
|
162
|
+
one either. It exists so a future edit cannot reintroduce the dev failure on
|
|
163
|
+
the VPC path, which has no relay boundary to catch it.
|
|
164
|
+
|
|
165
|
+
**What still cannot be observed locally:** headers appended after `fetch()`
|
|
166
|
+
leaves the module. That is now evidenced rather than assumed, but the evidence
|
|
167
|
+
is a point-in-time deploy — re-run the turn above if Cloudflare changes VPC
|
|
168
|
+
egress behaviour, since Workers VPC is still beta.
|
|
169
|
+
|
|
170
|
+
A `403` there is diagnosable in one glance: `codex_egress_forbidden`
|
|
171
|
+
(`disconnected`) is the front door and names the hop-header cause;
|
|
172
|
+
`codex_account_forbidden` (`reauth_required`) is the Codex API refusing the
|
|
173
|
+
account. Per step 4 above, a front-door 403 is a platform blocker — do not
|
|
174
|
+
answer it with browser headers or a direct-fetch retry.
|
|
175
|
+
|
|
176
|
+
Set `appConfig.ai.provider` to `openai-codex-subscription`. Create
|
|
177
|
+
`RAYBASE_OPENAI_SUBSCRIPTION_KEY` with at least 32 random bytes encoded as
|
|
178
|
+
base64/base64url and store it only as a local/deployed Worker secret. Never add
|
|
179
|
+
its value to Wrangler configuration, settings, source, or manifests.
|
|
180
|
+
|
|
181
|
+
Credential modes are explicit:
|
|
182
|
+
|
|
183
|
+
- `operator`: one designated responsible operator subject.
|
|
184
|
+
- `user`: each signed-in user links their own eligible subscription.
|
|
185
|
+
- `organization`: an organization-controlled account, scoped to the resolved
|
|
186
|
+
organization runtime.
|
|
187
|
+
|
|
188
|
+
`protocol_update_required` means update to a newly published
|
|
189
|
+
`@raybase/openai-subscription` version containing a reviewed upstream
|
|
190
|
+
pin/schema/fixture update. Do not edit the production allowlist or retry
|
|
191
|
+
blindly. A `protocol:codex_400_<token>` fingerprint without a health field is a
|
|
192
|
+
request rejection instead: fix the caller-owned request or schema and use a
|
|
193
|
+
bounded `oaiRequestId`, when present on the thrown error, for upstream support
|
|
194
|
+
correlation. For key rotation, deploy the reviewed replacement key and
|
|
195
|
+
reconnect each scope; old ciphertext is intentionally not decryptable under an
|
|
196
|
+
unrelated key.
|
|
197
|
+
|
|
198
|
+
The module informs operators about usage scope; it does not certify or enforce
|
|
199
|
+
eligibility.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
event: response.created
|
|
2
|
+
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_function_fixture","status":"in_progress","output":[]}}
|
|
3
|
+
|
|
4
|
+
event: response.output_item.added
|
|
5
|
+
data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"id":"fc_function_fixture","type":"function_call","status":"in_progress","arguments":"","call_id":"call_function_fixture","name":"lookup_customer"}}
|
|
6
|
+
|
|
7
|
+
event: response.function_call_arguments.delta
|
|
8
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":2,"item_id":"fc_function_fixture","output_index":0,"delta":"{\"customerId\":"}
|
|
9
|
+
|
|
10
|
+
event: response.function_call_arguments.delta
|
|
11
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":3,"item_id":"fc_function_fixture","output_index":0,"delta":"\"cust_fixture\","}
|
|
12
|
+
|
|
13
|
+
event: response.function_call_arguments.delta
|
|
14
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":4,"item_id":"fc_function_fixture","output_index":0,"delta":"\"includeOrders\":true}"}
|
|
15
|
+
|
|
16
|
+
event: response.function_call_arguments.done
|
|
17
|
+
data: {"type":"response.function_call_arguments.done","sequence_number":5,"item_id":"fc_function_fixture","output_index":0,"name":"lookup_customer","arguments":"{\"customerId\":\"cust_fixture\",\"includeOrders\":true}"}
|
|
18
|
+
|
|
19
|
+
event: response.output_item.done
|
|
20
|
+
data: {"type":"response.output_item.done","sequence_number":6,"output_index":0,"item":{"id":"fc_function_fixture","type":"function_call","status":"completed","arguments":"{\"customerId\":\"cust_fixture\",\"includeOrders\":true}","call_id":"call_function_fixture","name":"lookup_customer"}}
|
|
21
|
+
|
|
22
|
+
event: response.completed
|
|
23
|
+
data: {"type":"response.completed","sequence_number":7,"response":{"id":"resp_function_fixture","status":"completed","output":[{"id":"fc_function_fixture","type":"function_call","status":"completed","arguments":"{\"customerId\":\"cust_fixture\",\"includeOrders\":true}","call_id":"call_function_fixture","name":"lookup_customer"}],"usage":{"input_tokens":12,"input_tokens_details":{"cached_tokens":0},"output_tokens":8,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":20}}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
event: response.created
|
|
2
|
+
data: {"type":"response.created","response":{"id":"resp_image_fixture","status":"in_progress"}}
|
|
3
|
+
|
|
4
|
+
event: response.output_item.added
|
|
5
|
+
data: {"type":"response.output_item.added","item":{"id":"msg_image_fixture","type":"message","role":"assistant"}}
|
|
6
|
+
|
|
7
|
+
event: response.output_text.delta
|
|
8
|
+
data: {"type":"response.output_text.delta","delta":"fixture image answer"}
|
|
9
|
+
|
|
10
|
+
event: response.output_text.done
|
|
11
|
+
data: {"type":"response.output_text.done","text":"fixture image answer"}
|
|
12
|
+
|
|
13
|
+
event: response.completed
|
|
14
|
+
data: {"type":"response.completed","response":{"id":"resp_image_fixture","status":"completed","usage":{"input_tokens":19,"output_tokens":3}}}
|
|
15
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"model": "gpt-5.6-terra",
|
|
3
|
+
"instructions": "Answer briefly.",
|
|
4
|
+
"input": [
|
|
5
|
+
{
|
|
6
|
+
"role": "user",
|
|
7
|
+
"content": [
|
|
8
|
+
{
|
|
9
|
+
"type": "input_text",
|
|
10
|
+
"text": "Compare these images: "
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "input_image",
|
|
14
|
+
"image_url": "https://example.test/fixture.png"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "input_text",
|
|
18
|
+
"text": " and "
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "input_image",
|
|
22
|
+
"image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"tools": [],
|
|
28
|
+
"tool_choice": "none",
|
|
29
|
+
"parallel_tool_calls": true,
|
|
30
|
+
"stream": true,
|
|
31
|
+
"store": false
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
event: response.created
|
|
2
|
+
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_mixed_fixture","status":"in_progress","output":[]}}
|
|
3
|
+
|
|
4
|
+
event: response.output_item.added
|
|
5
|
+
data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"id":"msg_mixed_fixture","type":"message","status":"in_progress","role":"assistant","content":[]}}
|
|
6
|
+
|
|
7
|
+
event: response.content_part.added
|
|
8
|
+
data: {"type":"response.content_part.added","sequence_number":2,"item_id":"msg_mixed_fixture","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}
|
|
9
|
+
|
|
10
|
+
event: response.output_text.delta
|
|
11
|
+
data: {"type":"response.output_text.delta","sequence_number":3,"item_id":"msg_mixed_fixture","output_index":0,"content_index":0,"delta":"I'll check "}
|
|
12
|
+
|
|
13
|
+
event: response.output_text.delta
|
|
14
|
+
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_mixed_fixture","output_index":0,"content_index":0,"delta":"the order."}
|
|
15
|
+
|
|
16
|
+
event: response.output_text.done
|
|
17
|
+
data: {"type":"response.output_text.done","sequence_number":5,"item_id":"msg_mixed_fixture","output_index":0,"content_index":0,"text":"I'll check the order."}
|
|
18
|
+
|
|
19
|
+
event: response.content_part.done
|
|
20
|
+
data: {"type":"response.content_part.done","sequence_number":6,"item_id":"msg_mixed_fixture","output_index":0,"content_index":0,"part":{"type":"output_text","text":"I'll check the order.","annotations":[]}}
|
|
21
|
+
|
|
22
|
+
event: response.output_item.done
|
|
23
|
+
data: {"type":"response.output_item.done","sequence_number":7,"output_index":0,"item":{"id":"msg_mixed_fixture","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"I'll check the order.","annotations":[]}]}}
|
|
24
|
+
|
|
25
|
+
event: response.output_item.added
|
|
26
|
+
data: {"type":"response.output_item.added","sequence_number":8,"output_index":1,"item":{"id":"fc_mixed_fixture","type":"function_call","status":"in_progress","arguments":"","call_id":"call_mixed_fixture","name":"lookup_order"}}
|
|
27
|
+
|
|
28
|
+
event: response.function_call_arguments.delta
|
|
29
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":9,"item_id":"fc_mixed_fixture","output_index":1,"delta":"{\"orderId\":"}
|
|
30
|
+
|
|
31
|
+
event: response.function_call_arguments.delta
|
|
32
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":10,"item_id":"fc_mixed_fixture","output_index":1,"delta":"\"order_fixture\"}"}
|
|
33
|
+
|
|
34
|
+
event: response.function_call_arguments.done
|
|
35
|
+
data: {"type":"response.function_call_arguments.done","sequence_number":11,"item_id":"fc_mixed_fixture","output_index":1,"name":"lookup_order","arguments":"{\"orderId\":\"order_fixture\"}"}
|
|
36
|
+
|
|
37
|
+
event: response.output_item.done
|
|
38
|
+
data: {"type":"response.output_item.done","sequence_number":12,"output_index":1,"item":{"id":"fc_mixed_fixture","type":"function_call","status":"completed","arguments":"{\"orderId\":\"order_fixture\"}","call_id":"call_mixed_fixture","name":"lookup_order"}}
|
|
39
|
+
|
|
40
|
+
event: response.completed
|
|
41
|
+
data: {"type":"response.completed","sequence_number":13,"response":{"id":"resp_mixed_fixture","status":"completed","output":[{"id":"msg_mixed_fixture","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"I'll check the order.","annotations":[]}]},{"id":"fc_mixed_fixture","type":"function_call","status":"completed","arguments":"{\"orderId\":\"order_fixture\"}","call_id":"call_mixed_fixture","name":"lookup_order"}],"usage":{"input_tokens":21,"input_tokens_details":{"cached_tokens":4},"output_tokens":14,"output_tokens_details":{"reasoning_tokens":3},"total_tokens":35}}}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
event: response.created
|
|
2
|
+
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_multi_tool_fixture","status":"in_progress","output":[]}}
|
|
3
|
+
|
|
4
|
+
event: response.output_item.added
|
|
5
|
+
data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"id":"fc_weather_fixture","type":"function_call","status":"in_progress","arguments":"","call_id":"call_weather_fixture","name":"lookup_weather"}}
|
|
6
|
+
|
|
7
|
+
event: response.output_item.added
|
|
8
|
+
data: {"type":"response.output_item.added","sequence_number":2,"output_index":1,"item":{"id":"fc_time_fixture","type":"function_call","status":"in_progress","arguments":"","call_id":"call_time_fixture","name":"lookup_time"}}
|
|
9
|
+
|
|
10
|
+
event: response.function_call_arguments.delta
|
|
11
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":3,"item_id":"fc_weather_fixture","output_index":0,"delta":"{\"city\":\"Par"}
|
|
12
|
+
|
|
13
|
+
event: response.function_call_arguments.delta
|
|
14
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":4,"item_id":"fc_time_fixture","output_index":1,"delta":"{\"timezone\":"}
|
|
15
|
+
|
|
16
|
+
event: response.function_call_arguments.delta
|
|
17
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":5,"item_id":"fc_weather_fixture","output_index":0,"delta":"is\",\"units\":\"celsius\"}"}
|
|
18
|
+
|
|
19
|
+
event: response.function_call_arguments.delta
|
|
20
|
+
data: {"type":"response.function_call_arguments.delta","sequence_number":6,"item_id":"fc_time_fixture","output_index":1,"delta":"\"Europe/Paris\"}"}
|
|
21
|
+
|
|
22
|
+
event: response.function_call_arguments.done
|
|
23
|
+
data: {"type":"response.function_call_arguments.done","sequence_number":7,"item_id":"fc_time_fixture","output_index":1,"name":"lookup_time","arguments":"{\"timezone\":\"Europe/Paris\"}"}
|
|
24
|
+
|
|
25
|
+
event: response.function_call_arguments.done
|
|
26
|
+
data: {"type":"response.function_call_arguments.done","sequence_number":8,"item_id":"fc_weather_fixture","output_index":0,"name":"lookup_weather","arguments":"{\"city\":\"Paris\",\"units\":\"celsius\"}"}
|
|
27
|
+
|
|
28
|
+
event: response.output_item.done
|
|
29
|
+
data: {"type":"response.output_item.done","sequence_number":9,"output_index":1,"item":{"id":"fc_time_fixture","type":"function_call","status":"completed","arguments":"{\"timezone\":\"Europe/Paris\"}","call_id":"call_time_fixture","name":"lookup_time"}}
|
|
30
|
+
|
|
31
|
+
event: response.output_item.done
|
|
32
|
+
data: {"type":"response.output_item.done","sequence_number":10,"output_index":0,"item":{"id":"fc_weather_fixture","type":"function_call","status":"completed","arguments":"{\"city\":\"Paris\",\"units\":\"celsius\"}","call_id":"call_weather_fixture","name":"lookup_weather"}}
|
|
33
|
+
|
|
34
|
+
event: response.completed
|
|
35
|
+
data: {"type":"response.completed","sequence_number":11,"response":{"id":"resp_multi_tool_fixture","status":"completed","output":[{"id":"fc_weather_fixture","type":"function_call","status":"completed","arguments":"{\"city\":\"Paris\",\"units\":\"celsius\"}","call_id":"call_weather_fixture","name":"lookup_weather"},{"id":"fc_time_fixture","type":"function_call","status":"completed","arguments":"{\"timezone\":\"Europe/Paris\"}","call_id":"call_time_fixture","name":"lookup_time"}],"usage":{"input_tokens":31,"input_tokens_details":{"cached_tokens":6},"output_tokens":19,"output_tokens_details":{"reasoning_tokens":2},"total_tokens":50}}}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
event: response.created
|
|
2
|
+
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_text_fixture","status":"in_progress","output":[]}}
|
|
3
|
+
|
|
4
|
+
event: response.in_progress
|
|
5
|
+
data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_text_fixture","status":"in_progress","output":[]}}
|
|
6
|
+
|
|
7
|
+
event: response.output_item.added
|
|
8
|
+
data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_text_fixture","type":"message","status":"in_progress","role":"assistant","content":[]}}
|
|
9
|
+
|
|
10
|
+
event: response.content_part.added
|
|
11
|
+
data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_text_fixture","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}
|
|
12
|
+
|
|
13
|
+
event: response.output_text.delta
|
|
14
|
+
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_text_fixture","output_index":0,"content_index":0,"delta":"fixture"}
|
|
15
|
+
|
|
16
|
+
event: response.output_text.done
|
|
17
|
+
data: {"type":"response.output_text.done","sequence_number":5,"item_id":"msg_text_fixture","output_index":0,"content_index":0,"text":"fixture"}
|
|
18
|
+
|
|
19
|
+
event: response.content_part.done
|
|
20
|
+
data: {"type":"response.content_part.done","sequence_number":6,"item_id":"msg_text_fixture","output_index":0,"content_index":0,"part":{"type":"output_text","text":"fixture","annotations":[]}}
|
|
21
|
+
|
|
22
|
+
event: response.output_item.done
|
|
23
|
+
data: {"type":"response.output_item.done","sequence_number":7,"output_index":0,"item":{"id":"msg_text_fixture","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"fixture","annotations":[]}]}}
|
|
24
|
+
|
|
25
|
+
event: response.completed
|
|
26
|
+
data: {"type":"response.completed","sequence_number":8,"response":{"id":"resp_text_fixture","status":"completed","output":[{"id":"msg_text_fixture","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"fixture","annotations":[]}]}],"usage":{"input_tokens":3,"input_tokens_details":{"cached_tokens":0},"output_tokens":1,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":4}}}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"moduleProtocolVersion": "codex-subscription-2026-07-29",
|
|
4
|
+
"observedAt": "2026-07-29",
|
|
5
|
+
"upstream": {
|
|
6
|
+
"repository": "https://github.com/openai/codex",
|
|
7
|
+
"commit": "df326d31cda41a84f0641ec6519db08020248281",
|
|
8
|
+
"evidence": [
|
|
9
|
+
"codex-rs/login/src/device_code_auth.rs",
|
|
10
|
+
"codex-rs/login/src/auth/manager.rs",
|
|
11
|
+
"codex-rs/model-provider-info/src/lib.rs",
|
|
12
|
+
"codex-rs/model-provider/src/auth.rs",
|
|
13
|
+
"codex-rs/codex-api/src/endpoint/responses.rs",
|
|
14
|
+
"codex-rs/codex-api/src/sse/responses.rs"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"productionAllowlist": [
|
|
18
|
+
{ "origin": "https://auth.openai.com", "method": "POST", "path": "/api/accounts/deviceauth/usercode" },
|
|
19
|
+
{ "origin": "https://auth.openai.com", "method": "POST", "path": "/api/accounts/deviceauth/token" },
|
|
20
|
+
{ "origin": "https://auth.openai.com", "method": "POST", "path": "/oauth/token" },
|
|
21
|
+
{ "origin": "https://chatgpt.com", "method": "POST", "path": "/backend-api/codex/responses" }
|
|
22
|
+
],
|
|
23
|
+
"requestContentEvidence": {
|
|
24
|
+
"observedAt": "2026-08-11",
|
|
25
|
+
"scope": "Redacted local image-input probe only; authentication and SSE protocol pins were not re-reviewed.",
|
|
26
|
+
"wireField": "input_image.image_url",
|
|
27
|
+
"completedMediaTypes": ["image/png", "image/jpeg", "image/webp", "image/gif"],
|
|
28
|
+
"completedDetailValues": ["omitted", "auto", "low", "high", "original"],
|
|
29
|
+
"qualityResult": "400 unknown_parameter",
|
|
30
|
+
"largestCompletedEncodedRequestBytes": 19573864,
|
|
31
|
+
"largestCompletedDecodedImageBytes": 14680144,
|
|
32
|
+
"exactPrivateLimitVerified": false,
|
|
33
|
+
"fixtures": ["responses-image-request.json", "responses-image-input.sse"]
|
|
34
|
+
},
|
|
35
|
+
"fixtures": {
|
|
36
|
+
"provenance": "Sanitized synthetic fixtures derived from the pinned Rust response structs, SSE event matches, and observed machine-readable Codex refusal envelopes. They contain no live credentials, identifiers, prompts, completions, schema names, or request IDs.",
|
|
37
|
+
"inventory": {
|
|
38
|
+
"successfulSse": [
|
|
39
|
+
"responses.sse",
|
|
40
|
+
"responses-function-call.sse",
|
|
41
|
+
"responses-mixed.sse",
|
|
42
|
+
"responses-multi-tool.sse"
|
|
43
|
+
],
|
|
44
|
+
"sanitizedErrorEnvelopes": [
|
|
45
|
+
"responses-invalid-json-schema.json",
|
|
46
|
+
"responses-invalid-function-parameters.json"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OpenAISubscriptionCredentialScope } from "./types.js";
|
|
2
|
+
export type OpenAISubscriptionEncryptedEnvelope = {
|
|
3
|
+
version: 1;
|
|
4
|
+
keyVersion: 1;
|
|
5
|
+
nonce: string;
|
|
6
|
+
ciphertext: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function validateOpenAISubscriptionSecret(secret: string | undefined): Uint8Array;
|
|
9
|
+
export declare function encryptOpenAISubscriptionEnvelope(secret: string, scope: OpenAISubscriptionCredentialScope, value: unknown): Promise<OpenAISubscriptionEncryptedEnvelope>;
|
|
10
|
+
export declare function decryptOpenAISubscriptionEnvelope<T>(secret: string, scope: OpenAISubscriptionCredentialScope, envelope: OpenAISubscriptionEncryptedEnvelope): Promise<T>;
|
|
11
|
+
export declare function openAISubscriptionScopeKey(scope: OpenAISubscriptionCredentialScope): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,MAAM,mCAAmC,GAAG;IAChD,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,CAAC,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAKF,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAgBvF;AAED,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iCAAiC,EACxC,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,mCAAmC,CAAC,CAc9C;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iCAAiC,EACxC,QAAQ,EAAE,mCAAmC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAED,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAG1G"}
|