Python 88.3%
TypeScript 7.6%
Shell 4.1%
1#!/usr/bin/env python32"""Build generated/fragments/**/openai-agents*.json for the OpenAI Agents Platform domain.34Sources: OpenAPI spec (authoritative for shapes) + docs pages + live probes (tmp-live/agents/*.json).5Run from repo root: python3 tmp/agents/build_fragments.py6"""7from __future__ import annotations8import json, re, sys9from pathlib import Path1011ROOT = Path(__file__).resolve().parents[2]12OPS = json.load(open(ROOT / 'tmp/agents/spec_ops.json'))13S = json.load(open(ROOT / 'tmp/agents/spec_schemas.json'))14FLOW = json.load(open(ROOT / 'tmp-live/agents/session-flow.json'))15PROBES = json.load(open(ROOT / 'tmp-live/agents/probes-list.json'))16MISC = json.load(open(ROOT / 'tmp-live/agents/probes-misc.json'))17TODAY = '2026-09-18'18RET = f'{TODAY}T00:00:00Z'19DOC = 'https://developers.openai.com/api/docs/'20REF = 'https://developers.openai.com/api/reference/'21SPEC_URL = 'https://github.com/openai/openai-openapi (openapi-master.yaml)'2223def src(*urls):24 return [{'url': u, 'retrieved_at': RET} for u in urls]2526# ---------------------------------------------------------------- SDK surface27def sdk_map(path_md):28 m = {}29 for line in open(path_md, encoding='utf-8'):30 mm = re.search(r'title="(get|post|delete|put|patch) ([^"]+)">(client\.[^<]*)<a[^>]*>([a-zA-Z_]+)</a>', line)31 if mm:32 key = f'{mm.group(1).upper()} {mm.group(2)}'33 m.setdefault(key, f'{mm.group(3)}{mm.group(4)}()')34 return m35PY = sdk_map(ROOT / 'sources/openai/openapi/python-sdk-api.md')36NODE = sdk_map(ROOT / 'sources/openai/openapi/node-sdk-api.md')3738# ---------------------------------------------------------------- helpers39def ref_name(o):40 return o['$ref'].split('/')[-1] if isinstance(o, dict) and '$ref' in o else None4142def body_ref(op):43 rb = op.get('requestBody')44 if not rb: return None, None45 for ct, v in rb.get('content', {}).items():46 return ct, ref_name(v.get('schema', {})) or v.get('schema')47 return None, None4849def resp_info(op):50 out = {}51 for code, r in (op.get('responses') or {}).items():52 c = r.get('content') or {}53 for ct, v in c.items():54 sch = v.get('schema', {})55 out[code] = {'content_type': ct, 'schema_ref': ref_name(sch) or ('inline' if sch else None)}56 break57 else:58 out[code] = {'content_type': None, 'schema_ref': None}59 return out6061def type_str(s, depth=0):62 if not isinstance(s, dict): return 'any'63 if '$ref' in s: return ref_name(s)64 if 'anyOf' in s or 'oneOf' in s:65 alts = s.get('anyOf') or s.get('oneOf')66 return ' | '.join(type_str(a, depth + 1) for a in alts)67 if 'allOf' in s: return ' & '.join(type_str(a, depth + 1) for a in s['allOf'])68 t = s.get('type')69 if isinstance(t, list): t = ' | '.join(str(x) for x in t)70 if t == 'array': return f'array<{type_str(s.get("items", {}), depth + 1)}>'71 if t == 'object' or 'properties' in s: return 'object'72 return t or 'any'7374def resolve(s):75 """Return (schema, name) following $ref."""76 name = None77 while isinstance(s, dict) and '$ref' in s:78 name = ref_name(s); s = S.get(name, {})79 return s, name8081# ---------------------------------------------------------------- live verification overlay82LIVE_OK = { # key -> (http_status, note)83 'GET /agents': (200, 'list agents with OpenAI-Beta: agents=v1 (empty list). Without the header: 400 invalid_beta'),84 'POST /agents': (201, 'created agent gpt-5.6-luna, instructions "Reply with OK." (gpt-5.4-nano rejected: "not supported by Managed Agents")'),85 'DELETE /agents/{agent_id}': (200, 'deleted probe agent -> object agent.deleted'),86 'GET /agents/sessions': (200, 'list sessions (empty list)'),87 'POST /agents/sessions': (201, 'created session env=none, agent_id, input "Reply with OK.", stream=true; SSE with 13 events ending agent.session.turn.completed; output "OK."'),88 'GET /agents/sessions/{session_id}': (200, 'retrieved session (status idle, usage null)'),89 'POST /agents/sessions/{session_id}': (200, 'updated metadata only'),90 'DELETE /agents/sessions/{session_id}': (200, 'deleted -> agent.session.deleted; later GET -> 404 not_found_error "No managed agent session found"'),91 'GET /agents/sessions/{session_id}/items': (200, '2 items (user message + assistant message phase final_answer)'),92 'GET /agents/sessions/{session_id}/turns': (200, '1 turn, status completed, usage null in list'),93 'GET /agents/sessions/{session_id}/turns/{turn_id}': (200, 'turn with usage input_tokens 6087 / output_tokens 6 (env none, no tools)'),94 'GET /agents/sessions/{session_id}/subagents': (200, 'empty list (multi_agent disabled)'),95 'GET /agents/sessions/{session_id}/artifacts': (200, 'empty list for environment.type none'),96 'GET /agents/sessions/{session_id}/events': (200, 'GET ...?stream=true opened before follow-up input; received 12 events for the second turn'),97 'POST /agents/sessions/{session_id}/events': (202, 'agent.session.input.message with Idempotency-Key -> 202 empty body; agent.session.input.cancel on idle session -> 202 empty body'),98 'GET /agents/environments/templates': (200, 'empty list'),99 'GET /agents/{agent_id}': (200, 'examples/create_agent.py: retrieved the agent just created'),100 'POST /agents/environments/templates': (201, 'examples/environment_template_create.*: created template (packages.python, setup_commands, network restricted + allowed_domains, env, inline file) -> envtmpl_…; setup_commands/env not echoed; inline file returned as {type,path,size_bytes}'),101 'GET /agents/environments/templates/{environment_template_id}': (200, 'examples/environment_template_create.*: retrieved template'),102 'POST /agents/environments/templates/{environment_template_id}': (200, 'examples/environment_template_create.py: updated name only; network preserved'),103 'DELETE /agents/environments/templates/{environment_template_id}': (200, 'examples/environment_template_create.*: -> object agent.environment.template.deleted'),104 'GET /vaults': (200, 'empty list'),105 'GET /chatkit/threads': (200, 'empty list with OpenAI-Beta: chatkit_beta=v1'),106}107LIVE_404 = {108 'GET /agents/environments/{environment_id}': (404, 'bogus id -> not_found_error "No managed agent resource found: env_doesnotexist" (route exists)'),109 'GET /vaults/{vault_id}': (404, 'bogus id -> not_found_error "No vault resource found: vault_doesnotexist" (route exists)'),110 'GET /chatkit/threads/{thread_id}': (404, 'bogus id -> invalid_request_error "ChatKit Thread with id \'cthr_doesnotexist\' not found." (route exists)'),111}112AGENTS_SCOPES = {'agents': ['api.agents.read', 'api.agents.write', 'api.responses.write (inference)'], 'vaults': ['api.vaults.read', 'api.vaults.write']}113114def relations_for(path):115 rel = []116 if path.startswith('/agents/sessions/{session_id}'):117 rel.append('belongs_to: agent.session (GET /agents/sessions/{session_id})')118 if '/subagents/{subagent_id}' in path: rel.append('belongs_to: agent.session.subagent')119 if '/turns/{turn_id}' in path: rel.append('belongs_to: agent.session.turn')120 if '/artifacts/{artifact_id}' in path: rel.append('belongs_to: agent.session.artifact')121 if path == '/agents/sessions':122 rel += ['references: agent (agent_id, optional)', 'references: agent.environment.template (environment.environment_template_id, optional)', 'references: vault (vault_ids[])',123 'has_many: agent.session.turn, agent.session.item, agent.session.subagent, agent.session.artifact, events']124 if path.startswith('/agents/environments/{environment_id}'):125 rel.append('belongs_to: agent.session (session.environment.id); file operations require status connected')126 if path.startswith('/agents/environments/templates'):127 rel.append('used_by: POST /agents/sessions (environment.environment_template_id, openai_hosted only)')128 if path.startswith('/vaults/{vault_id}/credentials'):129 rel.append('belongs_to: vault')130 if path.startswith('/vaults'):131 rel.append('used_by: POST /agents/sessions (vault_ids[]) ; MCP tool credential_id')132 if path == '/agents' or path == '/agents/{agent_id}':133 rel.append('used_by: POST /agents/sessions (agent_id); sessions copy the config at creation')134 if path.startswith('/chatkit/threads'):135 rel.append('belongs_to: chatkit.session scope (user); thread has_many chatkit.thread_item')136 if path.startswith('/chatkit/sessions'):137 rel.append('references: Agent Builder workflow (workflow.id) — Agent Builder shuts down 2026-11-30')138 return rel139140endpoints = []141def add_endpoint(key, op):142 method, path = key.split(' ', 1)143 full = '/v1' + path144 family = ('assistants' if path.startswith(('/assistants', '/threads')) else 'chatkit' if path.startswith('/chatkit') else 'vaults' if path.startswith('/vaults') else 'agents')145 ct, bref = body_ref(op)146 resp = resp_info(op)147 stream = key in ('POST /agents/sessions', 'GET /agents/sessions/{session_id}/events')148 status = ['DOCUMENTED']149 verification = {'method': 'docs_only', 'verified_at': TODAY, 'result': None, 'http_status': None, 'request_note': 'not called in this run'}150 beta = None151 if family in ('agents', 'vaults'):152 status.append('BETA'); beta = 'OpenAI-Beta: agents=v1'153 elif family == 'chatkit':154 status.append('BETA'); beta = 'OpenAI-Beta: chatkit_beta=v1'155 else:156 status = ['RETIRED', 'DOCUMENTED']; beta = 'OpenAI-Beta: assistants=v2 (historical)'157 if key in LIVE_OK:158 st, note = LIVE_OK[key]; status.append('LIVE_VERIFIED')159 verification = {'method': 'live_api', 'verified_at': TODAY, 'result': 'success', 'http_status': st, 'request_note': note}160 elif key in LIVE_404:161 st, note = LIVE_404[key]; status.append('LIVE_DISCOVERED')162 verification = {'method': 'live_api', 'verified_at': TODAY, 'result': 'success', 'http_status': st, 'request_note': note}163 if family == 'assistants':164 verification = {'method': 'live_api', 'verified_at': TODAY, 'result': 'failure', 'http_status': 404,165 'request_note': 'GET /v1/assistants?limit=1 and GET /v1/threads/thread_doesnotexist with OpenAI-Beta: assistants=v2 -> HTTP 404 with EMPTY body (no JSON error). API sunset 2026-08-26.'} if key in ('GET /assistants', 'GET /threads/{thread_id}') else \166 {'method': 'docs_only', 'verified_at': TODAY, 'result': 'failure', 'http_status': None, 'request_note': 'not called; sibling probes returned 404 empty body — API retired 2026-08-26'}167 pag = None168 params = op.get('parameters') or []169 qnames = [p['name'] for p in params if p.get('in') == 'query']170 if 'after' in qnames or 'page' in qnames:171 pag = {'style': 'cursor', 'params': qnames, 'response_fields': ['data', 'first_id', 'last_id', 'has_more'] if 'after' in qnames else ['data', 'next', 'has_more']}172 idem = None173 if any(p.get('name') == 'Idempotency-Key' for p in params): idem = 'Idempotency-Key request header (optional) makes retries of submitted input events idempotent'174 if key == 'DELETE /agents/sessions/{session_id}': idem = 'Idempotent delete; may return 409 while setup/execution is in progress — retry with a limit'175 ok_code = next((c for c in ('200', '201', '202') if c in resp), None)176 rec = {177 'provider': 'openai', 'api_family': f'agents-platform/{family}', 'method': method, 'path': full,178 'name': op.get('operationId'), 'summary': op.get('summary'), 'description': (op.get('description') or '').strip(),179 'status': status, 'auth': 'Bearer API key (Authorization: Bearer $OPENAI_API_KEY); restricted keys need ' + ', '.join(AGENTS_SCOPES['vaults'] if family == 'vaults' else AGENTS_SCOPES['agents']) if family in ('agents', 'vaults') else 'Bearer API key',180 'beta_header': beta, 'beta_gating': 'Required. Without the header the API returns 400 {"type":"invalid_beta","code":"invalid_beta"}; the query string ?beta=true is NOT accepted (live-tested).' if family == 'agents' or family == 'vaults' else ('Required per spec x-oaiMeta beta=true' if family == 'chatkit' else 'n/a (retired)'),181 'request': {'content_type': ct, 'body_ref': bref if isinstance(bref, str) else None,182 'path_params': [p['name'] for p in params if p.get('in') == 'path'], 'query_params': qnames,183 'header_params': [p['name'] for p in params if p.get('in') == 'header']},184 'response': {'success_status': int(ok_code) if ok_code else None, 'content_type': resp.get(ok_code, {}).get('content_type') if ok_code else None,185 'body_ref': resp.get(ok_code, {}).get('schema_ref') if ok_code else None, 'error_statuses': sorted(int(c) for c in resp if c not in ('200', '201', '202'))},186 'streaming': {'supported': stream, 'events_ref': 'generated/fragments/streaming-events/openai-agents.json' if stream else None,187 'note': ('POST /agents/sessions with "stream": true returns text/event-stream for the first turn; otherwise JSON SessionResource' if key == 'POST /agents/sessions' else 'GET ...?stream=true with Accept: text/event-stream; no replay of missed events; subagent turn events do not end the stream' if stream else None)},188 'pagination': pag, 'idempotency': idem,189 'sdk': {'python': PY.get(key), 'node': NODE.get(key)},190 'relations': relations_for(path),191 'deprecated_in_spec': bool(op.get('deprecated')),192 'verification': verification,193 'sources': src(REF + 'resources/beta/subresources/' + ('agents' if family in ('agents', 'vaults') else 'chatkit' if family == 'chatkit' else 'assistants'), SPEC_URL,194 *( [DOC + 'guides/agents-api/quickstart'] if family == 'agents' else [DOC + 'guides/agents-api/tools/vaults'] if family == 'vaults' else [DOC + 'guides/chatkit'] if family == 'chatkit' else [DOC + 'assistants/migration', DOC + 'deprecations#2025-08-20-assistants-api'])),195 }196 if family == 'assistants':197 rec['retirement'] = {'announced': '2025-08-26', 'retired': '2026-08-26', 'replacement': 'Responses API (+ Conversations API); Prompts replace Assistants; Items replace Run steps', 'live_observation': 'HTTP 404, empty body'}198 endpoints.append(rec)199200for key, op in OPS.items():201 add_endpoint(key, op)202203# docs-only endpoints not in the spec204endpoints.append({205 'provider': 'openai', 'api_family': 'agents-platform/agents', 'method': 'GET', 'path': '/v1/agents/sessions/{session_id}/traces',206 'name': 'listAgentSessionTraces (docs-only)', 'summary': 'Export session traces (OTLP JSON)',207 'description': 'Returns a page of traces for a session; each item carries `otlp.resourceSpans`. Trace export must be enabled for the organization; needs api.traces.read or api.agents.read. Traces are built after a turn ends.',208 'status': ['DOCUMENTED', 'BETA', 'LIVE_DISCOVERED'], 'auth': 'Bearer API key (api.traces.read or api.agents.read)', 'beta_header': 'OpenAI-Beta: agents=v1',209 'beta_gating': 'Required (agents=v1)', 'request': {'content_type': None, 'body_ref': None, 'path_params': ['session_id'], 'query_params': ['limit', 'order', 'after'], 'header_params': []},210 'response': {'success_status': 200, 'content_type': 'application/json', 'body_ref': 'inline: {object:"list", data:[{otlp:{resourceSpans:[...]}}], first_id, last_id, has_more}', 'error_statuses': [404]},211 'streaming': {'supported': False, 'events_ref': None, 'note': None}, 'pagination': {'style': 'cursor', 'params': ['limit', 'order', 'after'], 'response_fields': ['data', 'last_id', 'has_more']}, 'idempotency': None,212 'sdk': {'python': None, 'node': None}, 'relations': ['belongs_to: agent.session'], 'deprecated_in_spec': False, 'not_in_openapi_spec': True,213 'verification': {'method': 'live_api', 'verified_at': TODAY, 'result': 'success', 'http_status': 404, 'request_note': 'GET on a deleted session id -> 404 not_found_error "No managed agent resource found: sess_…" (route exists; org trace export not confirmed)'},214 'sources': src(DOC + 'guides/agents-api/tracing#export-session-traces'),215})216for m, p, name, desc, st in [217 ('POST', '/v1/workspace_agents/{id}/trigger', 'triggerWorkspaceAgent', 'Durably queue a trigger event for a published ChatGPT workspace agent (API channel id agtch_…). Returns 202 Accepted with conversation_url; with OpenAI-Beta: workspace_agent_runs=v1 also agent_trigger_run_id (apirun_…). The agent response itself is not retrievable via API.', 202),218 ('GET', '/v1/workspace_agents/{id}/runs/{run_id}', 'retrieveWorkspaceAgentRun', 'Poll a trigger run (beta): object workspace_agent.trigger_run with status queued|in_progress|suspended|completed|failed and error.code dispatch_failed|run_failed.', 200)]:219 endpoints.append({220 'provider': 'openai', 'api_family': 'agents-platform/workspace-agents', 'method': m, 'path': p, 'base_url': 'https://api.chatgpt.com', 'name': name, 'summary': name, 'description': desc,221 'status': ['DOCUMENTED', 'BETA', 'UNVERIFIED'], 'auth': 'Authorization: Bearer <Workspace Agent access token> (ChatGPT Admin > Access tokens, scope "Workspace Agents"; NOT a platform API key)',222 'beta_header': 'OpenAI-Beta: workspace_agent_runs=v1 (required to get agent_trigger_run_id / use runs polling)', 'beta_gating': 'Run polling is beta; trigger itself is GA-documented',223 'request': {'content_type': 'application/json' if m == 'POST' else None, 'body_ref': 'inline: {input (required), conversation_key (optional)}' if m == 'POST' else None, 'path_params': ['id'] + (['run_id'] if 'run_id' in p else []), 'query_params': [], 'header_params': ['Idempotency-Key'] if m == 'POST' else []},224 'response': {'success_status': st, 'content_type': 'application/json', 'body_ref': 'inline: {conversation_url, agent_trigger_run_id?}' if m == 'POST' else 'inline: workspace_agent.trigger_run', 'error_statuses': [401, 403, 404, 409]},225 'streaming': {'supported': False, 'events_ref': None, 'note': None}, 'pagination': None, 'idempotency': 'Idempotency-Key header: same key on the same trigger returns the original accepted outcome' if m == 'POST' else None,226 'sdk': {'python': None, 'node': None}, 'relations': ['workspace agent (agt_…) has_many api triggers (agtch_…) has_many trigger runs (apirun_…)'], 'deprecated_in_spec': False, 'not_in_openapi_spec': True,227 'verification': {'method': 'docs_only', 'verified_at': TODAY, 'result': 'restricted', 'http_status': None, 'request_note': 'Requires a ChatGPT Business/Enterprise/Edu workspace access token; not available with a platform API key — not attempted'},228 'sources': src('https://developers.openai.com/workspace-agents/trigger-runs', 'https://developers.openai.com/workspace-agents/authentication'),229 })230231(ROOT / 'generated/fragments/endpoints').mkdir(parents=True, exist_ok=True)232json.dump({'domain': 'openai-agents-platform', 'generated_at': RET, 'count': len(endpoints), 'records': endpoints}, open(ROOT / 'generated/fragments/endpoints/openai-agents.json', 'w'), indent=1, ensure_ascii=False)233print('endpoints', len(endpoints))234235# ---------------------------------------------------------------- parameters236params = []237def flatten(schema, prefix, endpoint, location, status, source, required_parent=True, depth=0, seen=None, variant=None, beta=None):238 seen = seen or set()239 sch, name = resolve(schema)240 if name and name in seen or depth > 6: return241 if name: seen = seen | {name}242 alts = sch.get('oneOf') or sch.get('anyOf')243 if alts and not sch.get('properties'):244 real = [a for a in alts if not (isinstance(a, dict) and a.get('type') == 'null' and len(a) == 1)]245 objs = [resolve(a) for a in real]246 # discriminated union of objects with const/enum `type`247 types = []248 for o, n in objs:249 t = (o.get('properties') or {}).get('type', {})250 if t.get('enum'): types.append(t['enum'][0])251 if types and len(types) == len(objs):252 for (o, n), t in zip(objs, types):253 flatten({'$ref': f'#/components/schemas/{n}'} if n else o, prefix, endpoint, location, status, source, required_parent, depth, seen, variant=f'{prefix}.type={t}' if prefix else f'type={t}', beta=beta)254 return255 # scalar/array alternatives -> one record256 rec = base_rec(prefix, endpoint, location, ' | '.join(type_str(a) for a in real), sch, status, source, required_parent, variant, beta)257 params.append(rec)258 for o, n in objs:259 if o.get('type') == 'array':260 flatten(o.get('items', {}), prefix + '[]', endpoint, location, status, source, False, depth + 1, seen, variant, beta)261 elif o.get('properties'):262 flatten({'$ref': f'#/components/schemas/{n}'} if n else o, prefix, endpoint, location, status, source, False, depth + 1, seen, variant, beta)263 return264 props = sch.get('properties') or {}265 req = set(sch.get('required') or [])266 if not props:267 if sch.get('type') == 'array':268 flatten(sch.get('items', {}), prefix + '[]', endpoint, location, status, source, False, depth + 1, seen, variant, beta)269 return270 for k, v in props.items():271 path = f'{prefix}.{k}' if prefix else k272 vs, vn = resolve(v)273 rec = base_rec(path, endpoint, location, type_str(v), vs if isinstance(v, dict) and '$ref' not in v else {**vs, 'description': v.get('description') or vs.get('description')}, status, source, k in req, variant, beta)274 if k == 'type' and vs.get('enum') and variant: rec['required'] = True275 params.append(rec)276 # descend277 if vs.get('type') == 'array' or (isinstance(vs.get('type'), list) and 'array' in vs['type']):278 flatten(vs.get('items', {}), path + '[]', endpoint, location, status, source, False, depth + 1, seen, variant, beta)279 elif vs.get('properties') or vs.get('oneOf') or vs.get('anyOf'):280 flatten({'$ref': f'#/components/schemas/{vn}'} if vn else vs, path, endpoint, location, status, source, False, depth + 1, seen, variant, beta)281282def base_rec(path, endpoint, location, typ, sch, status, source, required, variant, beta):283 d = (sch.get('description') or '').strip()284 if variant: d = f'[when {variant}] ' + d285 enum = sch.get('enum')286 if not enum and (sch.get('oneOf') or sch.get('anyOf')):287 alts = [resolve(a)[0] for a in (sch.get('oneOf') or sch.get('anyOf'))]288 e = [x for a in alts for x in (a.get('enum') or [])]289 enum = e or None290 rec = {'provider': 'openai', 'endpoint': endpoint, 'parameter': path, 'location': location, 'type': typ,291 'required': bool(required), 'default': sch.get('default'), 'minimum': sch.get('minimum'), 'maximum': sch.get('maximum'),292 'enum': enum, 'description': d, 'compatible_models': ['gpt-6-astra', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna (live-verified)'] if path in ('model', 'agent.model') else None,293 'beta_header': beta, 'status': status, 'source': source}294 for k in ('minLength', 'maxLength', 'maxItems'):295 if k in sch: rec[k] = sch[k]296 return rec297298def add_op_params(key, op, status, source, beta):299 method, path = key.split(' ', 1)300 ep = f'{method} /v1{path}'301 for p in op.get('parameters') or []:302 sch = p.get('schema') or {}303 rs, rn = resolve(sch)304 rec = base_rec(p['name'], ep, p['in'], type_str(sch), {**rs, 'description': p.get('description') or rs.get('description')}, status, source, p.get('required'), None, beta)305 params.append(rec)306 ct, bref = body_ref(op)307 if isinstance(bref, str):308 flatten({'$ref': f'#/components/schemas/{bref}'}, '', ep, 'body', status, source, True, 0, set(), None, beta)309310for key, op in OPS.items():311 path = key.split(' ', 1)[1]312 if path.startswith(('/assistants', '/threads')):313 # RETIRED: only top-level fields314 method = key.split(' ')[0]; ep = f'{method} /v1{path}'315 for p in op.get('parameters') or []:316 params.append(base_rec(p['name'], ep, p['in'], type_str(p.get('schema') or {}), {**resolve(p.get('schema') or {})[0], 'description': p.get('description')}, ['RETIRED', 'DOCUMENTED'], DOC + 'assistants/migration', p.get('required'), None, 'OpenAI-Beta: assistants=v2 (historical)'))317 ct, bref = body_ref(op)318 if isinstance(bref, str):319 sch = S.get(bref, {}); req = set(sch.get('required') or [])320 for k, v in (sch.get('properties') or {}).items():321 params.append(base_rec(k, ep, 'body', type_str(v), resolve(v)[0] if '$ref' in v else v, ['RETIRED', 'DOCUMENTED'], DOC + 'assistants/migration', k in req, None, 'OpenAI-Beta: assistants=v2 (historical)'))322 continue323 if path.startswith('/chatkit'):324 add_op_params(key, op, ['DOCUMENTED', 'BETA'], REF + 'resources/beta/subresources/chatkit', 'OpenAI-Beta: chatkit_beta=v1')325 elif path.startswith('/vaults'):326 add_op_params(key, op, ['DOCUMENTED', 'BETA'], DOC + 'guides/agents-api/tools/vaults', 'OpenAI-Beta: agents=v1')327 else:328 st = ['DOCUMENTED', 'BETA'] + (['LIVE_VERIFIED'] if key in LIVE_OK else [])329 add_op_params(key, op, st, REF + 'resources/beta/subresources/agents', 'OpenAI-Beta: agents=v1')330331# header params common to Agents API332for ep_family, hdr, desc in [('all Agents/Vaults endpoints', 'OpenAI-Beta', 'Must be `agents=v1`. Live: missing header -> 400 invalid_beta "To access the Agents API, set the \'OpenAI-Beta\' header to \'agents=v1\'."; `?beta=true` is not accepted.'),333 ('all ChatKit endpoints', 'OpenAI-Beta', 'Must be `chatkit_beta=v1` (spec examples).'),334 ('GET /v1/agents/sessions/{session_id}/events', 'Accept', 'text/event-stream recommended for the SSE stream (with ?stream=true).')]:335 params.append({'provider': 'openai', 'endpoint': ep_family, 'parameter': hdr, 'location': 'header', 'type': 'string', 'required': hdr == 'OpenAI-Beta', 'default': None, 'minimum': None, 'maximum': None, 'enum': None, 'description': desc, 'compatible_models': None, 'beta_header': None, 'status': ['DOCUMENTED', 'LIVE_VERIFIED'] if 'ChatKit' not in ep_family else ['DOCUMENTED', 'LIVE_VERIFIED'], 'source': DOC + 'guides/agents-api/quickstart'})336# workspace agents337WS = 'https://developers.openai.com/workspace-agents/trigger-runs'338for name, loc, typ, req, desc in [('id', 'path', 'string', True, 'Public API trigger identifier of the published API channel (agtch_…).'), ('run_id', 'path', 'string', True, 'Trigger run id (apirun_…) returned when OpenAI-Beta: workspace_agent_runs=v1 is sent.'),339 ('input', 'body', 'string', True, 'Message text passed to the agent as trigger input.'), ('conversation_key', 'body', 'string', False, 'Caller-defined stable identifier to continue the same agent conversation across trigger events.'),340 ('Idempotency-Key', 'header', 'string', False, 'Safe retry of the same trigger event; reuse only when retrying the same event.'), ('OpenAI-Beta', 'header', 'string', False, '`workspace_agent_runs=v1` to receive agent_trigger_run_id and enable run polling (beta).'),341 ('Authorization', 'header', 'string', True, 'Bearer Workspace Agent access token (ChatGPT admin access-token flow), scoped to Workspace Agents API only.')]:342 ep = 'GET https://api.chatgpt.com/v1/workspace_agents/{id}/runs/{run_id}' if name == 'run_id' else 'POST https://api.chatgpt.com/v1/workspace_agents/{id}/trigger'343 params.append({'provider': 'openai', 'endpoint': ep, 'parameter': name, 'location': loc, 'type': typ, 'required': req, 'default': None, 'minimum': None, 'maximum': None, 'enum': None, 'description': desc, 'compatible_models': None, 'beta_header': 'OpenAI-Beta: workspace_agent_runs=v1' if name in ('run_id',) else None, 'status': ['DOCUMENTED', 'UNVERIFIED'], 'source': WS})344345# de-duplicate records produced by nullable unions (oneOf[X, null]) — keep the first (carries "| null")346_seen = set(); _dedup = []347for r in params:348 var = re.match(r'^\[when (.*?)\] ', r['description'] or '')349 k = (r['endpoint'], r['parameter'], r['location'], var.group(1) if var else None)350 if k in _seen: continue351 _seen.add(k); _dedup.append(r)352params = _dedup353(ROOT / 'generated/fragments/parameters').mkdir(parents=True, exist_ok=True)354json.dump({'domain': 'openai-agents-platform', 'generated_at': RET, 'count': len(params), 'records': params}, open(ROOT / 'generated/fragments/parameters/openai-agents.json', 'w'), indent=1, ensure_ascii=False)355print('parameters', len(params))356357# ---------------------------------------------------------------- streaming events358live_examples = {}359for e in FLOW['create_session_stream']['events'] + FLOW['follow_up_stream']['events']:360 d = e.get('data')361 if isinstance(d, dict) and d.get('type') not in live_examples:362 live_examples[d['type']] = {'sse_event_line': e.get('event'), 'data': d}363live_order_first = [e['data']['type'] for e in FLOW['create_session_stream']['events'] if isinstance(e.get('data'), dict)]364# examples/send_turn_stream.sh kept the SSE connection open past turn.completed and observed agent.session.idle afterwards365LIVE_OBSERVED_TYPES_ONLY = {'agent.session.idle': 'observed in examples/openai/agents/send_turn_stream.sh (curl kept open 45 s): emitted right after agent.session.turn.completed; payload not captured'}366live_order_follow = [e['data']['type'] for e in FLOW['follow_up_stream']['events'] if isinstance(e.get('data'), dict)]367368def shrink(d):369 """Sanitized, compact example: trim nested session/turn payloads."""370 d = json.loads(json.dumps(d))371 if 'session' in d and isinstance(d['session'], dict):372 s = d['session']; d['session'] = {k: s[k] for k in ('id', 'object', 'status', 'required_actions', 'error') if k in s}; d['session']['agent'] = {'id': s['agent']['id'], 'model': s['agent']['model']}; d['session']['environment'] = s.get('environment'); d['session']['…'] = 'other SessionResource fields omitted'373 return d374375events = []376alts = S['SessionEvent']['oneOf']377for a in alts:378 n = ref_name(a); sch = S[n]; props = sch['properties']; t = props['type']['enum'][0]379 fields = {k: {'type': type_str(v), 'required': k in sch.get('required', []), 'description': (v.get('description') or resolve(v)[0].get('description') or '').strip()} for k, v in props.items()}380 ex = live_examples.get(t)381 rec = {'provider': 'openai', 'api': 'agents (Agents API sessions; SSE)', 'direction': 'server→client', 'event': t, 'description': (sch.get('description') or '').strip(),382 'schema': {'name': n, 'fields': fields}, 'status': ['DOCUMENTED', 'BETA'] + (['LIVE_VERIFIED'] if (ex or t in LIVE_OBSERVED_TYPES_ONLY) else []),383 'example': shrink(ex['data']) if ex else None, 'example_source': 'live 2026-09-18 (gpt-5.6-luna, environment none)' if ex else LIVE_OBSERVED_TYPES_ONLY.get(t, 'not observed live (requires tools/environment/subagents/reasoning summary or a failure)'),384 'terminal_for_turn': t in ('agent.session.turn.completed', 'agent.session.turn.failed', 'agent.session.turn.cancelled'),385 'source': REF + 'resources/beta/subresources/agents/streaming-events'}386 events.append(rec)387# client -> server input events388for n in S['SessionInputParam']['oneOf']:389 n = ref_name(n); sch = S[n]; props = sch['properties']; t = props['type']['enum'][0]390 fields = {k: {'type': type_str(v), 'required': k in sch.get('required', []), 'description': (v.get('description') or resolve(v)[0].get('description') or '').strip()} for k, v in props.items()}391 ex = {'agent.session.input.message': {'events': [{'type': 'agent.session.input.message', 'input': [{'role': 'user', 'content': [{'type': 'input_text', 'text': 'Reply with OK.'}]}]}]},392 'agent.session.input.cancel': {'events': [{'type': 'agent.session.input.cancel'}]},393 'agent.session.input.tool_result': {'events': [{'type': 'agent.session.input.tool_result', 'turn_id': 'turn_123', 'call_id': 'call_123', 'success': True, 'output': '{"found":true}'}]}}[t]394 events.append({'provider': 'openai', 'api': 'agents (POST /v1/agents/sessions/{session_id}/events)', 'direction': 'client→server', 'event': t, 'description': (sch.get('description') or '').strip(),395 'schema': {'name': n, 'fields': fields}, 'status': ['DOCUMENTED', 'BETA'] + (['LIVE_VERIFIED'] if t != 'agent.session.input.tool_result' else []),396 'example': ex, 'example_source': 'live 2026-09-18 -> HTTP 202 empty body' if t != 'agent.session.input.tool_result' else 'docs (tools/functions guide)',397 'source': DOC + 'guides/agents-api/sessions'})398# webhooks399for t, when in [('agent.session.created', 'A session is created (self-hosted: data.environment_id, data.environment_type, data.connect.remote_url).'),400 ('agent.session.action_required', 'The session needs a function result or an environment connection; data.required_action.type = function_call | environment_connection. Emitted BEFORE the API waits (up to 5 min) for the executor.'),401 ('agent.session.in_progress', 'The session starts processing a turn.'), ('agent.session.idle', 'Session idle and ready for input (not a success signal).'), ('agent.session.failed', 'Session entered failed state (not every failed turn).')]:402 events.append({'provider': 'openai', 'api': 'agents (webhooks — signed HTTP POST, verify with SDK webhooks.verify_signature)', 'direction': 'server→client (webhook)', 'event': t, 'description': when,403 'schema': {'name': 'webhook event', 'fields': {'id': {'type': 'string'}, 'object': {'type': '"event"'}, 'created_at': {'type': 'integer'}, 'type': {'type': 'string'}, 'data': {'type': 'object {id, environment_id?, environment_type?, connect?{remote_url}, required_action?{type}}'}}},404 'status': ['DOCUMENTED', 'BETA'], 'example': {'id': 'evt_123', 'object': 'event', 'created_at': 1750287018, 'type': t, 'data': {'id': 'sess_abc123', **({'required_action': {'type': 'function_call'}} if 'action' in t else {})}}, 'example_source': 'docs', 'source': DOC + 'guides/agents-api/sessions/webhooks'})405(ROOT / 'generated/fragments/streaming-events').mkdir(parents=True, exist_ok=True)406json.dump({'domain': 'openai-agents-platform', 'generated_at': RET, 'count': len(events),407 'live_observed_sequences': {'POST /v1/agents/sessions stream=true (env none, no tools)': live_order_first, 'GET /v1/agents/sessions/{id}/events?stream=true then POST input.message': live_order_follow, 'same via curl kept open 45 s (examples/send_turn_stream.sh)': live_order_follow + ['agent.session.idle']},408 'live_usage_observed': {'turn_1': {'input_tokens': 6087, 'cached_tokens': 0, 'output_tokens': 5}, 'turn_2': {'input_tokens': 6102, 'cached_tokens': 6084, 'output_tokens': 5}, 'turn_3': {'input_tokens': 6117, 'cached_tokens': 6099, 'output_tokens': 5}, 'note': 'gpt-5.6-luna, environment none, instructions "Reply with OK."; ~6k tokens = harness base prompt; prompt cache hits from turn 2'},409 'records': events}, open(ROOT / 'generated/fragments/streaming-events/openai-agents.json', 'w'), indent=1, ensure_ascii=False)410print('events', len(events))411412# ---------------------------------------------------------------- objects413OBJ = {414 'agent': ('AgentResource', 'Agent (saved, reusable agent definition)'), 'agent.deleted': ('DeletedAgentResource', None),415 'agent.session': ('SessionResource', 'Session'), 'agent.session.deleted': ('DeletedSessionResource', None),416 'agent.session.turn': ('TurnResource', 'Turn'), 'agent.session.subagent': ('SubagentResource', 'Subagent'),417 'agent.session.artifact': ('SessionArtifactResource', 'Artifact'), 'agent.session.artifact.deleted': ('DeletedSessionArtifactResource', None),418 'agent.environment': ('PublicEnvironmentResource', 'Environment (live execution environment)'), 'agent.environment.file': ('EnvironmentFileResource', 'Environment file'),419 'agent.environment.template': ('EnvironmentTemplateResource', 'Environment template'), 'agent.environment.template.deleted': ('DeletedEnvironmentTemplateResource', None),420 'vault': ('VaultResource', 'Vault'), 'vault.deleted': ('DeletedVaultResource', None), 'vault.credential': ('VaultCredentialResource', 'Credential (secrets write-only)'), 'vault.credential.deleted': ('DeletedVaultCredentialResource', None),421 'chatkit.session': ('ChatSessionResource', 'ChatKit session'), 'chatkit.thread': ('ThreadResource', 'ChatKit thread'), 'chatkit.thread.deleted': ('DeletedThreadResource', None),422}423SUB = ['SessionAgentResource', 'EnvironmentResourceNone', 'EnvironmentResourceOpenaiHosted', 'EnvironmentResourceSelfHosted', 'SessionRequiredActionResourceFunctionCall', 'SessionRequiredActionResourceEnvironmentConnection',424 'TokenUsageResource', 'SessionTurnErrorResource', 'ReasoningResource', 'TextResource', 'MultiAgentConfigResource', 'NetworkPolicyResource', 'EnvironmentPackagesResource',425 'VaultCredentialAuthResourceStaticBearer', 'VaultCredentialAuthResourceMcpOauth', 'VaultCredentialAuthResourceEnvironmentVariable', 'SessionEnvironmentStateResource']426objects = []427def fields_of(n):428 sch = S[n]; req = set(sch.get('required') or [])429 out = []430 for k, v in (sch.get('properties') or {}).items():431 rs, rn = resolve(v)432 f = {'name': k, 'type': type_str(v), 'required': k in req, 'description': (v.get('description') or rs.get('description') or '').strip()}433 if rs.get('enum') and k != 'object': f['enum'] = rs['enum']434 if k == 'type' and rs.get('enum'): f['const'] = rs['enum'][0]435 out.append(f)436 return out437live_objs = {'agent': json.load(open(ROOT / 'tmp-live/agents/create-agent-attempts.json'))[-1]['body'], 'agent.session': FLOW['retrieve session']['body'], 'agent.session.turn': FLOW['retrieve turn']['body'],438 'agent.session.deleted': FLOW['delete_session']['body'], 'agent.deleted': FLOW['delete_agent']['body']}439for obj, (n, label) in OBJ.items():440 fam = 'chatkit' if obj.startswith('chatkit') else 'vaults' if obj.startswith('vault') else 'agents'441 objects.append({'provider': 'openai', 'api_family': f'agents-platform/{fam}', 'name': label or n, 'object': obj, 'schema_name': n, 'description': (S[n].get('description') or '').strip(),442 'fields': fields_of(n), 'status': ['DOCUMENTED', 'BETA'] + (['LIVE_VERIFIED'] if obj in live_objs else []), 'example': live_objs.get(obj), 'example_source': 'live 2026-09-18' if obj in live_objs else None,443 'sources': src(REF + 'resources/beta/subresources/' + ('chatkit' if fam == 'chatkit' else 'agents'), SPEC_URL)})444# item types445for n in [ref_name(a) for a in S['SessionTurnItemResource']['oneOf']]:446 t = S[n]['properties']['type']['enum'][0]447 ex = None448 if t == 'message':449 ex = FLOW['list items']['body']['data']450 objects.append({'provider': 'openai', 'api_family': 'agents-platform/agents', 'name': f'Session item: {t}', 'object': f'agent.session.item[type={t}]', 'schema_name': n, 'description': (S[n].get('description') or '').strip(),451 'fields': fields_of(n), 'status': ['DOCUMENTED', 'BETA'] + (['LIVE_VERIFIED'] if ex else []), 'example': ex, 'example_source': 'live 2026-09-18 (user + assistant message, phase final_answer)' if ex else None,452 'union': 'SessionTurnItemResource (GET /items, /subagents/{id}/items, event item.added); AgentOutputItemResource subset for item.done',453 'sources': src(REF + 'resources/beta/subresources/agents', SPEC_URL)})454# Message content variants (MessageItemResource.content[] union)455for n in ['MessageContentResourceInputText', 'MessageContentResourceInputImage', 'MessageContentResourceOutputText']:456 if n in S:457 objects.append({'provider': 'openai', 'api_family': 'agents-platform/agents', 'name': f'Message content part: {n}', 'object': 'agent.session.item[type=message].content[]', 'schema_name': n, 'description': (S[n].get('description') or '').strip(), 'fields': fields_of(n), 'status': ['DOCUMENTED', 'BETA', 'LIVE_VERIFIED'], 'example': None, 'sources': src(SPEC_URL)})458for n in SUB:459 objects.append({'provider': 'openai', 'api_family': 'agents-platform/' + ('vaults' if n.startswith('Vault') else 'agents'), 'name': n, 'object': None, 'schema_name': n, 'description': (S[n].get('description') or '').strip(), 'fields': fields_of(n), 'status': ['DOCUMENTED', 'BETA'], 'example': None, 'sources': src(SPEC_URL)})460# chatkit thread items461for n in [ref_name(a) for a in S['ThreadItem']['oneOf']]:462 t = S[n]['properties']['type']['enum'][0]463 objects.append({'provider': 'openai', 'api_family': 'agents-platform/chatkit', 'name': f'ChatKit thread item: {t}', 'object': 'chatkit.thread_item', 'schema_name': n, 'description': (S[n].get('description') or '').strip(), 'fields': fields_of(n), 'status': ['DOCUMENTED', 'BETA'], 'example': None, 'sources': src(REF + 'resources/beta/subresources/chatkit/subresources/threads', SPEC_URL)})464# enums465ENUMS = ['SessionStatusResource', 'TurnStatusResource', 'SubagentStatusResource', 'EnvironmentStatusResource', 'SessionEnvironmentStatusResource', 'SessionTurnErrorCodeResource', 'FunctionCallStatusResource', 'OutputItemStatusResource', 'MessagePhaseResource', 'ReasoningEffortParam', 'ServiceTierParam', 'NetworkAccessParam', 'McpConnectionOriginParam', 'WebSearchModeParam', 'VaultStatusParam', 'ChatSessionStatus']466for n in ENUMS:467 sch = S.get(n, {}); e = sch.get('enum') or [x for a in (sch.get('oneOf') or sch.get('anyOf') or []) for x in (resolve(a)[0].get('enum') or [])]468 objects.append({'provider': 'openai', 'api_family': 'agents-platform', 'name': f'enum {n}', 'object': None, 'schema_name': n, 'description': (sch.get('description') or '').strip(), 'enum': e, 'fields': [], 'status': ['DOCUMENTED', 'BETA'], 'sources': src(SPEC_URL)})469# workspace agents + assistants (retired) objects470objects.append({'provider': 'openai', 'api_family': 'agents-platform/workspace-agents', 'name': 'Workspace agent trigger run', 'object': 'workspace_agent.trigger_run', 'schema_name': None, 'description': 'Status of a trigger run on api.chatgpt.com (beta; requires OpenAI-Beta: workspace_agent_runs=v1).',471 'fields': [{'name': k, 'type': t, 'required': True, 'description': d} for k, t, d in [('object', '"workspace_agent.trigger_run"', ''), ('id', 'string (apirun_…)', ''), ('status', 'queued|in_progress|suspended|completed|failed', 'completed/failed terminal'), ('created_at', 'integer', ''), ('agent_id', 'string (agt_…)', ''), ('api_trigger_id', 'string (agtch_…)', ''), ('conversation_url', 'string', 'https://chatgpt.com/c/…'), ('error', 'null | {code: dispatch_failed|run_failed}', '')]],472 'status': ['DOCUMENTED', 'BETA', 'UNVERIFIED'], 'example': {'object': 'workspace_agent.trigger_run', 'id': 'apirun_123', 'status': 'in_progress', 'created_at': 1784763880, 'agent_id': 'agt_123', 'api_trigger_id': 'agtch_123', 'conversation_url': 'https://chatgpt.com/c/123', 'error': None}, 'sources': src('https://developers.openai.com/workspace-agents/trigger-runs')})473for n, obj in [('AssistantObject', 'assistant'), ('ThreadObject', 'thread'), ('MessageObject', 'thread.message'), ('RunObject', 'thread.run'), ('RunStepObject', 'thread.run.step')]:474 objects.append({'provider': 'openai', 'api_family': 'agents-platform/assistants', 'name': f'{obj} (RETIRED 2026-08-26)', 'object': obj, 'schema_name': n, 'description': (S[n].get('description') or '').strip()[:300],475 'fields': [{'name': k} for k in (S[n].get('properties') or {})], 'status': ['RETIRED', 'DOCUMENTED'], 'replacement': {'assistant': 'Prompt (dashboard) / Agent', 'thread': 'Conversation', 'thread.message': 'Conversation item', 'thread.run': 'Response', 'thread.run.step': 'Response output item'}[obj],476 'sources': src(DOC + 'assistants/migration', SPEC_URL)})477(ROOT / 'generated/fragments/objects').mkdir(parents=True, exist_ok=True)478json.dump({'domain': 'openai-agents-platform', 'generated_at': RET, 'count': len(objects), 'records': objects}, open(ROOT / 'generated/fragments/objects/openai-agents-objects.json', 'w'), indent=1, ensure_ascii=False)479print('objects', len(objects))480