mirror of
https://github.com/openai/codex.git
synced 2026-03-07 23:23:20 +00:00
Compare commits
4 Commits
dev/flaky-
...
app-server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f70d55fae | ||
|
|
b7b1de7892 | ||
|
|
eea6b2d2df | ||
|
|
b89a794304 |
@@ -956,40 +956,6 @@
|
||||
],
|
||||
"title": "ChatgptLoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptAccountId": {
|
||||
"description": "Workspace/account identifier supplied by the client.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptPlanType": {
|
||||
"description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptAuthTokens"
|
||||
],
|
||||
"title": "ChatgptAuthTokensLoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"chatgptAccountId",
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptAuthTokensLoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3901,106 +3901,6 @@
|
||||
"title": "FuzzyFileSearch/sessionCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/started"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeStartedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/itemAdded"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeItemAddedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/outputAudio/delta"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeOutputAudioDeltaNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/error"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeErrorNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/closed"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeClosedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.",
|
||||
"properties": {
|
||||
|
||||
@@ -7538,106 +7538,6 @@
|
||||
"title": "FuzzyFileSearch/sessionCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/started"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadRealtimeStartedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/itemAdded"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadRealtimeItemAddedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/outputAudio/delta"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadRealtimeOutputAudioDeltaNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/error"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadRealtimeErrorNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/closed"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadRealtimeClosedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.",
|
||||
"properties": {
|
||||
@@ -11362,40 +11262,6 @@
|
||||
],
|
||||
"title": "Chatgptv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptAccountId": {
|
||||
"description": "Workspace/account identifier supplied by the client.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptPlanType": {
|
||||
"description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptAuthTokens"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"chatgptAccountId",
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
|
||||
@@ -7613,40 +7613,6 @@
|
||||
],
|
||||
"title": "Chatgptv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptAccountId": {
|
||||
"description": "Workspace/account identifier supplied by the client.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptPlanType": {
|
||||
"description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptAuthTokens"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"chatgptAccountId",
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
@@ -11505,106 +11471,6 @@
|
||||
"title": "FuzzyFileSearch/sessionCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/started"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeStartedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/startedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/itemAdded"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeItemAddedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/itemAddedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/outputAudio/delta"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeOutputAudioDeltaNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/outputAudio/deltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/error"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeErrorNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/errorNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/closed"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeClosedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/closedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.",
|
||||
"properties": {
|
||||
|
||||
@@ -36,40 +36,6 @@
|
||||
],
|
||||
"title": "Chatgptv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptAccountId": {
|
||||
"description": "Workspace/account identifier supplied by the client.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgptPlanType": {
|
||||
"description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptAuthTokens"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"chatgptAccountId",
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
|
||||
@@ -31,11 +31,6 @@ import type { TerminalInteractionNotification } from "./v2/TerminalInteractionNo
|
||||
import type { ThreadArchivedNotification } from "./v2/ThreadArchivedNotification";
|
||||
import type { ThreadClosedNotification } from "./v2/ThreadClosedNotification";
|
||||
import type { ThreadNameUpdatedNotification } from "./v2/ThreadNameUpdatedNotification";
|
||||
import type { ThreadRealtimeClosedNotification } from "./v2/ThreadRealtimeClosedNotification";
|
||||
import type { ThreadRealtimeErrorNotification } from "./v2/ThreadRealtimeErrorNotification";
|
||||
import type { ThreadRealtimeItemAddedNotification } from "./v2/ThreadRealtimeItemAddedNotification";
|
||||
import type { ThreadRealtimeOutputAudioDeltaNotification } from "./v2/ThreadRealtimeOutputAudioDeltaNotification";
|
||||
import type { ThreadRealtimeStartedNotification } from "./v2/ThreadRealtimeStartedNotification";
|
||||
import type { ThreadStartedNotification } from "./v2/ThreadStartedNotification";
|
||||
import type { ThreadStatusChangedNotification } from "./v2/ThreadStatusChangedNotification";
|
||||
import type { ThreadTokenUsageUpdatedNotification } from "./v2/ThreadTokenUsageUpdatedNotification";
|
||||
@@ -50,4 +45,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
|
||||
/**
|
||||
* Notification sent from the server to the client.
|
||||
*/
|
||||
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
|
||||
export type ServerNotification ={ "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
|
||||
|
||||
@@ -2,20 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt" } | { "type": "chatgptAuthTokens",
|
||||
/**
|
||||
* Access token (JWT) supplied by the client.
|
||||
* This token is used for backend API requests and email extraction.
|
||||
*/
|
||||
accessToken: string,
|
||||
/**
|
||||
* Workspace/account identifier supplied by the client.
|
||||
*/
|
||||
chatgptAccountId: string,
|
||||
/**
|
||||
* Optional plan type supplied by the client.
|
||||
*
|
||||
* When `null`, Codex attempts to derive the plan type from access-token
|
||||
* claims. If unavailable, the plan defaults to `unknown`.
|
||||
*/
|
||||
chatgptPlanType?: string | null, };
|
||||
export type LoginAccountParams ={ "type": "apiKey", apiKey: string, } | { "type": "chatgpt" };
|
||||
|
||||
@@ -23,6 +23,36 @@ pub fn experimental_fields() -> Vec<&'static ExperimentalField> {
|
||||
inventory::iter::<ExperimentalField>.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Describes how an experimental enum variant appears on the wire.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExperimentalEnumVariantEncoding {
|
||||
/// A plain string-literal union arm, such as `"chatgptAuthTokens"`.
|
||||
StringLiteral,
|
||||
/// A tagged object arm, such as `{ "type": "chatgptAuthTokens", ... }`.
|
||||
TaggedObject { tag_name: &'static str },
|
||||
/// An externally tagged object arm, such as `{ "reject": { ... } }`.
|
||||
ExternallyTaggedObject,
|
||||
}
|
||||
|
||||
/// Describes an experimental enum variant on a specific type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ExperimentalEnumVariant {
|
||||
pub type_name: &'static str,
|
||||
pub serialized_name: &'static str,
|
||||
pub reason: &'static str,
|
||||
pub encoding: ExperimentalEnumVariantEncoding,
|
||||
}
|
||||
|
||||
inventory::collect!(ExperimentalEnumVariant);
|
||||
|
||||
/// Returns all experimental enum variants registered across the protocol
|
||||
/// types.
|
||||
pub fn experimental_enum_variants() -> Vec<&'static ExperimentalEnumVariant> {
|
||||
inventory::iter::<ExperimentalEnumVariant>
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Constructs a consistent error message for experimental gating.
|
||||
pub fn experimental_required_message(reason: &str) -> String {
|
||||
format!("{reason} requires experimentalApi capability")
|
||||
@@ -31,8 +61,11 @@ pub fn experimental_required_message(reason: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ExperimentalApi as ExperimentalApiTrait;
|
||||
use super::ExperimentalEnumVariantEncoding;
|
||||
use super::experimental_enum_variants;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde::Serialize;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(ExperimentalApi)]
|
||||
@@ -67,4 +100,31 @@ mod tests {
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(ExperimentalApi, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
enum TaggedEnumVariantShapes {
|
||||
Stable,
|
||||
#[experimental("tagged/experimental")]
|
||||
ExperimentalVariant,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_registers_experimental_enum_variants_with_wire_shape_metadata() {
|
||||
let variant = experimental_enum_variants()
|
||||
.into_iter()
|
||||
.find(|variant| variant.reason == "tagged/experimental")
|
||||
.expect("tagged experimental variant should be registered");
|
||||
|
||||
assert_eq!(
|
||||
*variant,
|
||||
crate::experimental_api::ExperimentalEnumVariant {
|
||||
type_name: "TaggedEnumVariantShapes",
|
||||
serialized_name: "experimentalVariant",
|
||||
reason: "tagged/experimental",
|
||||
encoding: ExperimentalEnumVariantEncoding::TaggedObject { tag_name: "type" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ use crate::ClientNotification;
|
||||
use crate::ClientRequest;
|
||||
use crate::ServerNotification;
|
||||
use crate::ServerRequest;
|
||||
use crate::experimental_api::ExperimentalEnumVariant;
|
||||
use crate::experimental_api::ExperimentalEnumVariantEncoding;
|
||||
use crate::experimental_api::experimental_enum_variants;
|
||||
use crate::experimental_api::experimental_fields;
|
||||
use crate::export_client_notification_schemas;
|
||||
use crate::export_client_param_schemas;
|
||||
@@ -220,6 +223,7 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -
|
||||
|
||||
fn filter_experimental_ts(out_dir: &Path) -> Result<()> {
|
||||
let registered_fields = experimental_fields();
|
||||
let registered_enum_variants = experimental_enum_variants();
|
||||
let experimental_method_types = experimental_method_types();
|
||||
// Most generated TS files are filtered by schema processing, but
|
||||
// `ClientRequest.ts` and any type with `#[experimental(...)]` fields need
|
||||
@@ -227,6 +231,7 @@ fn filter_experimental_ts(out_dir: &Path) -> Result<()> {
|
||||
// file-local unions/interfaces.
|
||||
filter_client_request_ts(out_dir, EXPERIMENTAL_CLIENT_METHODS)?;
|
||||
filter_experimental_type_fields_ts(out_dir, ®istered_fields)?;
|
||||
filter_experimental_enum_variants_ts(out_dir, ®istered_enum_variants)?;
|
||||
remove_generated_type_files(out_dir, &experimental_method_types, "ts")?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -327,15 +332,163 @@ fn filter_experimental_fields_in_ts_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_experimental_enum_variants_ts(
|
||||
out_dir: &Path,
|
||||
experimental_variants: &[&'static ExperimentalEnumVariant],
|
||||
) -> Result<()> {
|
||||
let mut variants_by_type_name: HashMap<String, Vec<&'static ExperimentalEnumVariant>> =
|
||||
HashMap::new();
|
||||
for variant in experimental_variants {
|
||||
variants_by_type_name
|
||||
.entry(variant.type_name.to_string())
|
||||
.or_default()
|
||||
.push(*variant);
|
||||
}
|
||||
|
||||
for path in ts_files_in_recursive(out_dir)? {
|
||||
let Some(type_name) = path.file_stem().and_then(|stem| stem.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
let Some(type_variants) = variants_by_type_name.get(type_name) else {
|
||||
continue;
|
||||
};
|
||||
filter_experimental_enum_variants_in_ts_file(&path, type_variants)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_experimental_enum_variants_in_ts_file(
|
||||
path: &Path,
|
||||
experimental_variants: &[&ExperimentalEnumVariant],
|
||||
) -> Result<()> {
|
||||
let mut content =
|
||||
fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?;
|
||||
let Some((prefix, body, suffix)) = split_type_alias(&content) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let filtered_arms: Vec<String> = split_top_level(&body, '|')
|
||||
.into_iter()
|
||||
.filter(|arm| {
|
||||
!experimental_variants
|
||||
.iter()
|
||||
.any(|variant| ts_union_arm_matches_experimental_enum_variant(arm, variant))
|
||||
})
|
||||
.collect();
|
||||
let new_body = filtered_arms.join(" | ");
|
||||
content = format!("{prefix}{new_body}{suffix}");
|
||||
let import_usage_scope = split_type_alias(&content)
|
||||
.map(|(_, body, _)| body)
|
||||
.unwrap_or_else(|| new_body.clone());
|
||||
content = prune_unused_type_imports(content, &import_usage_scope);
|
||||
fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_experimental_schema(bundle: &mut Value) -> Result<()> {
|
||||
let registered_fields = experimental_fields();
|
||||
let registered_enum_variants = experimental_enum_variants();
|
||||
filter_experimental_fields_in_root(bundle, ®istered_fields);
|
||||
filter_experimental_fields_in_definitions(bundle, ®istered_fields);
|
||||
filter_experimental_enum_variants_in_root(bundle, ®istered_enum_variants);
|
||||
filter_experimental_enum_variants_in_definitions(bundle, ®istered_enum_variants);
|
||||
prune_experimental_methods(bundle, EXPERIMENTAL_CLIENT_METHODS);
|
||||
remove_experimental_method_type_definitions(bundle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter_experimental_enum_variants_in_root(
|
||||
schema: &mut Value,
|
||||
experimental_variants: &[&'static ExperimentalEnumVariant],
|
||||
) {
|
||||
let Some(title) = schema.get("title").and_then(Value::as_str) else {
|
||||
return;
|
||||
};
|
||||
let title = title.to_string();
|
||||
|
||||
let matching_variants: Vec<&ExperimentalEnumVariant> = experimental_variants
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|variant| title == variant.type_name)
|
||||
.collect();
|
||||
if matching_variants.is_empty() {
|
||||
return;
|
||||
}
|
||||
remove_experimental_enum_variants_from_schema(schema, &matching_variants);
|
||||
}
|
||||
|
||||
fn filter_experimental_enum_variants_in_definitions(
|
||||
bundle: &mut Value,
|
||||
experimental_variants: &[&'static ExperimentalEnumVariant],
|
||||
) {
|
||||
let Some(definitions) = bundle.get_mut("definitions").and_then(Value::as_object_mut) else {
|
||||
return;
|
||||
};
|
||||
|
||||
filter_experimental_enum_variants_in_definitions_map(definitions, experimental_variants);
|
||||
}
|
||||
|
||||
fn filter_experimental_enum_variants_in_definitions_map(
|
||||
definitions: &mut Map<String, Value>,
|
||||
experimental_variants: &[&'static ExperimentalEnumVariant],
|
||||
) {
|
||||
for (def_name, def_schema) in definitions.iter_mut() {
|
||||
if is_namespace_map(def_schema) {
|
||||
if let Some(namespace_defs) = def_schema.as_object_mut() {
|
||||
filter_experimental_enum_variants_in_definitions_map(
|
||||
namespace_defs,
|
||||
experimental_variants,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let matching_variants: Vec<&ExperimentalEnumVariant> = experimental_variants
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|variant| definition_matches_type(def_name, variant.type_name))
|
||||
.collect();
|
||||
if matching_variants.is_empty() {
|
||||
continue;
|
||||
}
|
||||
remove_experimental_enum_variants_from_schema(def_schema, &matching_variants);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_experimental_enum_variants_from_schema(
|
||||
schema: &mut Value,
|
||||
experimental_variants: &[&ExperimentalEnumVariant],
|
||||
) {
|
||||
match schema {
|
||||
Value::Array(items) => {
|
||||
items.retain(|item| {
|
||||
!experimental_variants
|
||||
.iter()
|
||||
.any(|variant| json_schema_matches_experimental_enum_variant(item, variant))
|
||||
});
|
||||
for item in items {
|
||||
remove_experimental_enum_variants_from_schema(item, experimental_variants);
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
if let Some(enum_values) = map.get_mut("enum").and_then(Value::as_array_mut) {
|
||||
enum_values.retain(|value| {
|
||||
!experimental_variants.iter().any(|variant| {
|
||||
matches!(
|
||||
variant.encoding,
|
||||
ExperimentalEnumVariantEncoding::StringLiteral
|
||||
) && value.as_str() == Some(variant.serialized_name)
|
||||
})
|
||||
});
|
||||
}
|
||||
for child in map.values_mut() {
|
||||
remove_experimental_enum_variants_from_schema(child, experimental_variants);
|
||||
}
|
||||
}
|
||||
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_experimental_fields_in_root(
|
||||
schema: &mut Value,
|
||||
experimental_fields: &[&'static crate::experimental_api::ExperimentalField],
|
||||
@@ -715,6 +868,105 @@ fn parse_property(input: &str) -> Option<(String, &str)> {
|
||||
Some((name, input[colon_index + 1..].trim_start()))
|
||||
}
|
||||
|
||||
fn ts_union_arm_matches_experimental_enum_variant(
|
||||
arm: &str,
|
||||
variant: &ExperimentalEnumVariant,
|
||||
) -> bool {
|
||||
let arm = strip_leading_block_comments(arm).trim();
|
||||
match variant.encoding {
|
||||
ExperimentalEnumVariantEncoding::StringLiteral => {
|
||||
arm == format!("\"{}\"", variant.serialized_name)
|
||||
}
|
||||
ExperimentalEnumVariantEncoding::TaggedObject { tag_name } => {
|
||||
ts_object_arm_has_string_property(arm, tag_name, variant.serialized_name)
|
||||
}
|
||||
ExperimentalEnumVariantEncoding::ExternallyTaggedObject => {
|
||||
ts_object_arm_has_property(arm, variant.serialized_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ts_object_arm_has_string_property(arm: &str, property_name: &str, property_value: &str) -> bool {
|
||||
let Some((open, close)) = find_top_level_brace_span(arm) else {
|
||||
return false;
|
||||
};
|
||||
let inner = &arm[open + 1..close];
|
||||
for field in split_top_level(inner, ',') {
|
||||
let field = strip_leading_block_comments(field.as_str());
|
||||
let Some((name, value)) = parse_property(field) else {
|
||||
continue;
|
||||
};
|
||||
if name != property_name {
|
||||
continue;
|
||||
}
|
||||
return parse_string_literal(strip_leading_block_comments(value).trim_start())
|
||||
.is_some_and(|(value, _)| value == property_value);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn ts_object_arm_has_property(arm: &str, property_name: &str) -> bool {
|
||||
let Some((open, close)) = find_top_level_brace_span(arm) else {
|
||||
return false;
|
||||
};
|
||||
let inner = &arm[open + 1..close];
|
||||
split_top_level(inner, ',').into_iter().any(|field| {
|
||||
let field = strip_leading_block_comments(field.as_str());
|
||||
parse_property_name(field).is_some_and(|name| name == property_name)
|
||||
})
|
||||
}
|
||||
|
||||
fn json_schema_matches_experimental_enum_variant(
|
||||
schema: &Value,
|
||||
variant: &ExperimentalEnumVariant,
|
||||
) -> bool {
|
||||
match variant.encoding {
|
||||
ExperimentalEnumVariantEncoding::StringLiteral => {
|
||||
json_schema_matches_string_literal(schema, variant.serialized_name)
|
||||
}
|
||||
ExperimentalEnumVariantEncoding::TaggedObject { tag_name } => {
|
||||
json_schema_has_tagged_variant(schema, tag_name, variant.serialized_name)
|
||||
}
|
||||
ExperimentalEnumVariantEncoding::ExternallyTaggedObject => {
|
||||
json_schema_has_property(schema, variant.serialized_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn json_schema_matches_string_literal(schema: &Value, literal: &str) -> bool {
|
||||
let Value::Object(map) = schema else {
|
||||
return false;
|
||||
};
|
||||
if map.get("const").and_then(Value::as_str) == Some(literal) {
|
||||
return true;
|
||||
}
|
||||
map.get("enum")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(|values| values.len() == 1 && values[0].as_str() == Some(literal))
|
||||
}
|
||||
|
||||
fn json_schema_has_tagged_variant(schema: &Value, tag_name: &str, tag_value: &str) -> bool {
|
||||
let Value::Object(map) = schema else {
|
||||
return false;
|
||||
};
|
||||
let Some(properties) = map.get("properties").and_then(Value::as_object) else {
|
||||
return false;
|
||||
};
|
||||
properties
|
||||
.get(tag_name)
|
||||
.is_some_and(|tag_schema| json_schema_matches_string_literal(tag_schema, tag_value))
|
||||
}
|
||||
|
||||
fn json_schema_has_property(schema: &Value, property_name: &str) -> bool {
|
||||
let Value::Object(map) = schema else {
|
||||
return false;
|
||||
};
|
||||
let Some(properties) = map.get("properties").and_then(Value::as_object) else {
|
||||
return false;
|
||||
};
|
||||
properties.contains_key(property_name)
|
||||
}
|
||||
|
||||
fn strip_leading_block_comments(input: &str) -> &str {
|
||||
let mut rest = input.trim_start();
|
||||
loop {
|
||||
@@ -1942,6 +2194,12 @@ mod tests {
|
||||
let thread_start_ts =
|
||||
fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?;
|
||||
assert_eq!(thread_start_ts.contains("mockExperimentalField"), false);
|
||||
let login_account_params_ts =
|
||||
fs::read_to_string(output_dir.join("v2").join("LoginAccountParams.ts"))?;
|
||||
assert_eq!(
|
||||
login_account_params_ts.contains("\"chatgptAuthTokens\""),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
output_dir
|
||||
.join("v2")
|
||||
@@ -2197,6 +2455,12 @@ mod tests {
|
||||
let thread_start_ts =
|
||||
fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?;
|
||||
assert_eq!(thread_start_ts.contains("mockExperimentalField"), true);
|
||||
let login_account_params_ts =
|
||||
fs::read_to_string(output_dir.join("v2").join("LoginAccountParams.ts"))?;
|
||||
assert_eq!(
|
||||
login_account_params_ts.contains("\"chatgptAuthTokens\""),
|
||||
true
|
||||
);
|
||||
let command_execution_request_approval_ts = fs::read_to_string(
|
||||
output_dir
|
||||
.join("v2")
|
||||
@@ -2662,6 +2926,12 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k
|
||||
let thread_start_json =
|
||||
fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.json"))?;
|
||||
assert_eq!(thread_start_json.contains("mockExperimentalField"), false);
|
||||
let login_account_params_json =
|
||||
fs::read_to_string(output_dir.join("v2").join("LoginAccountParams.json"))?;
|
||||
assert_eq!(
|
||||
login_account_params_json.contains("chatgptAuthTokens"),
|
||||
false
|
||||
);
|
||||
let command_execution_request_approval_json =
|
||||
fs::read_to_string(output_dir.join("CommandExecutionRequestApprovalParams.json"))?;
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::JSONRPCNotification;
|
||||
use crate::JSONRPCRequest;
|
||||
use crate::RequestId;
|
||||
use crate::experimental_api::ExperimentalEnumVariantEncoding;
|
||||
use crate::experimental_api::experimental_enum_variants;
|
||||
use crate::export::GeneratedSchema;
|
||||
use crate::export::write_json_schema;
|
||||
use crate::protocol::v1;
|
||||
@@ -11,6 +15,7 @@ use codex_experimental_api_macros::ExperimentalApi;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use strum_macros::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -639,6 +644,89 @@ macro_rules! client_notification_definitions {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn filter_experimental_thread_items_in_server_notification(
|
||||
notification: ServerNotification,
|
||||
) -> Option<ServerNotification> {
|
||||
match notification {
|
||||
ServerNotification::ThreadStarted(mut notification) => {
|
||||
notification.thread.strip_experimental_thread_items();
|
||||
Some(ServerNotification::ThreadStarted(notification))
|
||||
}
|
||||
ServerNotification::ItemStarted(notification) if notification.item.is_experimental() => {
|
||||
None
|
||||
}
|
||||
ServerNotification::ItemCompleted(notification) if notification.item.is_experimental() => {
|
||||
None
|
||||
}
|
||||
ServerNotification::TurnStarted(mut notification) => {
|
||||
notification.turn.strip_experimental_thread_items();
|
||||
Some(ServerNotification::TurnStarted(notification))
|
||||
}
|
||||
ServerNotification::TurnCompleted(mut notification) => {
|
||||
notification.turn.strip_experimental_thread_items();
|
||||
Some(ServerNotification::TurnCompleted(notification))
|
||||
}
|
||||
_ => Some(notification),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transport serializes app-server responses before per-connection filtering,
|
||||
/// so stable clients need a last-mile scrub pass over the response JSON.
|
||||
pub fn strip_experimental_thread_items_from_serialized_response(result: &mut Value) {
|
||||
match result {
|
||||
Value::Object(map) => {
|
||||
for (key, value) in map {
|
||||
if key == "items" {
|
||||
if let Value::Array(items) = value {
|
||||
items.retain(|item| !is_experimental_thread_item_value(item));
|
||||
for item in items {
|
||||
strip_experimental_thread_items_from_serialized_response(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strip_experimental_thread_items_from_serialized_response(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(values) => {
|
||||
for value in values {
|
||||
strip_experimental_thread_items_from_serialized_response(value);
|
||||
}
|
||||
}
|
||||
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_experimental_thread_item_value(value: &Value) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
Value::Object(map)
|
||||
if matches!(
|
||||
map.get("type"),
|
||||
Some(Value::String(kind))
|
||||
if experimental_thread_item_variant_tags().contains(kind.as_str())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn experimental_thread_item_variant_tags() -> &'static HashSet<&'static str> {
|
||||
static TAGS: OnceLock<HashSet<&'static str>> = OnceLock::new();
|
||||
TAGS.get_or_init(|| {
|
||||
experimental_enum_variants()
|
||||
.into_iter()
|
||||
.filter_map(|variant| match variant {
|
||||
crate::experimental_api::ExperimentalEnumVariant {
|
||||
type_name: "ThreadItem",
|
||||
serialized_name,
|
||||
encoding: ExperimentalEnumVariantEncoding::TaggedObject { tag_name: "type" },
|
||||
..
|
||||
} => Some(*serialized_name),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
impl TryFrom<JSONRPCRequest> for ServerRequest {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
|
||||
@@ -3633,6 +3633,24 @@ impl ThreadItem {
|
||||
| ThreadItem::ContextCompaction { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_experimental(&self) -> bool {
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(self).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn strip_experimental_thread_items(&mut self) {
|
||||
for turn in &mut self.turns {
|
||||
turn.strip_experimental_thread_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Turn {
|
||||
pub fn strip_experimental_thread_items(&mut self) {
|
||||
self.items.retain(|item| !item.is_experimental());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
|
||||
@@ -7,6 +7,8 @@ use crate::outgoing_message::OutgoingMessage;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::filter_experimental_thread_items_in_server_notification;
|
||||
use codex_app_server_protocol::strip_experimental_thread_items_from_serialized_response;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use owo_colors::OwoColorize;
|
||||
@@ -584,7 +586,9 @@ async fn send_message_to_connection(
|
||||
warn!("dropping message for disconnected connection: {connection_id:?}");
|
||||
return false;
|
||||
};
|
||||
let message = filter_outgoing_message_for_connection(connection_state, message);
|
||||
let Some(message) = filter_outgoing_message_for_connection(connection_state, message) else {
|
||||
return false;
|
||||
};
|
||||
if should_skip_notification_for_connection(connection_state, &message) {
|
||||
return false;
|
||||
}
|
||||
@@ -613,7 +617,7 @@ async fn send_message_to_connection(
|
||||
fn filter_outgoing_message_for_connection(
|
||||
connection_state: &OutboundConnectionState,
|
||||
message: OutgoingMessage,
|
||||
) -> OutgoingMessage {
|
||||
) -> Option<OutgoingMessage> {
|
||||
let experimental_api_enabled = connection_state
|
||||
.experimental_api_enabled
|
||||
.load(Ordering::Acquire);
|
||||
@@ -625,12 +629,25 @@ fn filter_outgoing_message_for_connection(
|
||||
if !experimental_api_enabled {
|
||||
params.strip_experimental_fields();
|
||||
}
|
||||
OutgoingMessage::Request(ServerRequest::CommandExecutionRequestApproval {
|
||||
request_id,
|
||||
params,
|
||||
})
|
||||
Some(OutgoingMessage::Request(
|
||||
ServerRequest::CommandExecutionRequestApproval { request_id, params },
|
||||
))
|
||||
}
|
||||
_ => message,
|
||||
OutgoingMessage::AppServerNotification(notification) => {
|
||||
if experimental_api_enabled {
|
||||
Some(OutgoingMessage::AppServerNotification(notification))
|
||||
} else {
|
||||
filter_experimental_thread_items_in_server_notification(notification)
|
||||
.map(OutgoingMessage::AppServerNotification)
|
||||
}
|
||||
}
|
||||
OutgoingMessage::Response(mut response) => {
|
||||
if !experimental_api_enabled {
|
||||
strip_experimental_thread_items_from_serialized_response(&mut response.result);
|
||||
}
|
||||
Some(OutgoingMessage::Response(response))
|
||||
}
|
||||
_ => Some(message),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ use syn::LitStr;
|
||||
use syn::Type;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
#[derive(Default)]
|
||||
struct EnumSerdeConfig {
|
||||
rename_all: Option<String>,
|
||||
tag: Option<String>,
|
||||
untagged: bool,
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ExperimentalApi, attributes(experimental))]
|
||||
pub fn derive_experimental_api(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
@@ -141,7 +148,10 @@ fn derive_for_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream {
|
||||
|
||||
fn derive_for_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
|
||||
let name = &input.ident;
|
||||
let type_name_lit = LitStr::new(&name.to_string(), Span::call_site());
|
||||
let serde_config = enum_serde_config(&input.attrs);
|
||||
let mut match_arms = Vec::new();
|
||||
let mut registrations = Vec::new();
|
||||
|
||||
for variant in &data.variants {
|
||||
let variant_name = &variant.ident;
|
||||
@@ -155,6 +165,14 @@ fn derive_for_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
|
||||
match_arms.push(quote! {
|
||||
#pattern => Some(#reason),
|
||||
});
|
||||
if let Some(registration) = experimental_enum_variant_registration(
|
||||
&type_name_lit,
|
||||
&serde_config,
|
||||
variant,
|
||||
&reason,
|
||||
) {
|
||||
registrations.push(registration);
|
||||
}
|
||||
} else {
|
||||
match_arms.push(quote! {
|
||||
#pattern => None,
|
||||
@@ -163,6 +181,8 @@ fn derive_for_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream {
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#(#registrations)*
|
||||
|
||||
impl crate::experimental_api::ExperimentalApi for #name {
|
||||
fn experimental_reason(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
@@ -181,12 +201,160 @@ fn experimental_reason(attrs: &[Attribute]) -> Option<LitStr> {
|
||||
attr.parse_args::<LitStr>().ok()
|
||||
}
|
||||
|
||||
fn enum_serde_config(attrs: &[Attribute]) -> EnumSerdeConfig {
|
||||
let mut config = EnumSerdeConfig::default();
|
||||
for attr in attrs.iter().filter(|attr| attr.path().is_ident("serde")) {
|
||||
let _ = attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("rename_all") {
|
||||
config.rename_all = Some(meta.value()?.parse::<LitStr>()?.value());
|
||||
} else if meta.path.is_ident("tag") {
|
||||
config.tag = Some(meta.value()?.parse::<LitStr>()?.value());
|
||||
} else if meta.path.is_ident("untagged") {
|
||||
config.untagged = true;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
fn variant_serialized_name(variant: &syn::Variant, rename_all: Option<&str>) -> String {
|
||||
if let Some(rename) = serde_rename(&variant.attrs) {
|
||||
return rename;
|
||||
}
|
||||
apply_rename_all(&variant.ident.to_string(), rename_all)
|
||||
}
|
||||
|
||||
fn serde_rename(attrs: &[Attribute]) -> Option<String> {
|
||||
let mut rename = None;
|
||||
for attr in attrs.iter().filter(|attr| attr.path().is_ident("serde")) {
|
||||
let _ = attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("rename") {
|
||||
rename = Some(meta.value()?.parse::<LitStr>()?.value());
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
rename
|
||||
}
|
||||
|
||||
fn experimental_enum_variant_registration(
|
||||
type_name_lit: &LitStr,
|
||||
serde_config: &EnumSerdeConfig,
|
||||
variant: &syn::Variant,
|
||||
reason: &LitStr,
|
||||
) -> Option<proc_macro2::TokenStream> {
|
||||
if serde_config.untagged {
|
||||
return None;
|
||||
}
|
||||
|
||||
let serialized_name = variant_serialized_name(variant, serde_config.rename_all.as_deref());
|
||||
let serialized_name_lit = LitStr::new(&serialized_name, Span::call_site());
|
||||
let encoding = if let Some(tag_name) = serde_config.tag.as_deref() {
|
||||
let tag_name_lit = LitStr::new(tag_name, Span::call_site());
|
||||
quote! {
|
||||
crate::experimental_api::ExperimentalEnumVariantEncoding::TaggedObject {
|
||||
tag_name: #tag_name_lit,
|
||||
}
|
||||
}
|
||||
} else if matches!(variant.fields, Fields::Unit) {
|
||||
quote! {
|
||||
crate::experimental_api::ExperimentalEnumVariantEncoding::StringLiteral
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
crate::experimental_api::ExperimentalEnumVariantEncoding::ExternallyTaggedObject
|
||||
}
|
||||
};
|
||||
|
||||
Some(quote! {
|
||||
::inventory::submit! {
|
||||
crate::experimental_api::ExperimentalEnumVariant {
|
||||
type_name: #type_name_lit,
|
||||
serialized_name: #serialized_name_lit,
|
||||
reason: #reason,
|
||||
encoding: #encoding,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn field_serialized_name(field: &Field) -> Option<String> {
|
||||
let ident = field.ident.as_ref()?;
|
||||
let name = ident.to_string();
|
||||
Some(snake_to_camel(&name))
|
||||
}
|
||||
|
||||
fn apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
|
||||
let words = split_words(name);
|
||||
match rename_all {
|
||||
Some("camelCase") => {
|
||||
let mut out = String::new();
|
||||
for (index, word) in words.iter().enumerate() {
|
||||
if index == 0 {
|
||||
out.push_str(word);
|
||||
} else {
|
||||
out.push_str(&capitalize(word));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Some("snake_case") => words.join("_"),
|
||||
Some("kebab-case") => words.join("-"),
|
||||
Some("PascalCase") => words
|
||||
.iter()
|
||||
.map(|word| capitalize(word))
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
Some("SCREAMING_SNAKE_CASE") => words.join("_").to_ascii_uppercase(),
|
||||
Some("UPPERCASE") => words.concat().to_ascii_uppercase(),
|
||||
Some("lowercase") => words.concat(),
|
||||
Some(_) | None => name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_words(name: &str) -> Vec<String> {
|
||||
let mut words = Vec::new();
|
||||
let mut current = String::new();
|
||||
let chars: Vec<char> = name.chars().collect();
|
||||
for (index, ch) in chars.iter().copied().enumerate() {
|
||||
if ch == '_' || ch == '-' {
|
||||
if !current.is_empty() {
|
||||
words.push(std::mem::take(&mut current));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let previous = chars.get(index.wrapping_sub(1)).copied();
|
||||
let next = chars.get(index + 1).copied();
|
||||
let boundary_before = previous.is_some_and(|previous| {
|
||||
(previous.is_ascii_lowercase() && ch.is_ascii_uppercase())
|
||||
|| (previous.is_ascii_uppercase()
|
||||
&& ch.is_ascii_uppercase()
|
||||
&& next.is_some_and(|next| next.is_ascii_lowercase()))
|
||||
});
|
||||
if boundary_before && !current.is_empty() {
|
||||
words.push(std::mem::take(&mut current));
|
||||
}
|
||||
current.push(ch.to_ascii_lowercase());
|
||||
}
|
||||
if !current.is_empty() {
|
||||
words.push(current);
|
||||
}
|
||||
words
|
||||
}
|
||||
|
||||
fn capitalize(word: &str) -> String {
|
||||
let mut chars = word.chars();
|
||||
let Some(first) = chars.next() else {
|
||||
return String::new();
|
||||
};
|
||||
let mut out = String::new();
|
||||
out.push(first.to_ascii_uppercase());
|
||||
out.extend(chars);
|
||||
out
|
||||
}
|
||||
|
||||
fn snake_to_camel(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
let mut upper = false;
|
||||
|
||||
@@ -36,6 +36,9 @@ codex_rust_crate(
|
||||
],
|
||||
test_data_extra = [
|
||||
"config.schema.json",
|
||||
] + glob([
|
||||
"src/**/snapshots/**",
|
||||
]) + [
|
||||
# This is a bit of a hack, but empirically, some of our integration tests
|
||||
# are relying on the presence of this file as a repo root marker. When
|
||||
# running tests locally, this "just works," but in remote execution,
|
||||
|
||||
@@ -39,10 +39,15 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.expect("test setup should allow enabling request permissions");
|
||||
turn_context_raw
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("test setup should allow updating sandbox policy");
|
||||
// This test is about request-permissions validation, not managed sandbox
|
||||
// policy enforcement. Widen the derived sandbox policies directly so the
|
||||
// command runs without depending on a platform sandbox binary.
|
||||
turn_context_raw.file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
);
|
||||
turn_context_raw.network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(&SandboxPolicy::DangerFullAccess);
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
|
||||
|
||||
@@ -664,12 +664,16 @@ fn truncate_guardian_action_value(value: Value) -> Value {
|
||||
.map(truncate_guardian_action_value)
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Value::Object(values) => Value::Object(
|
||||
values
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key, truncate_guardian_action_value(value)))
|
||||
.collect(),
|
||||
),
|
||||
Value::Object(values) => {
|
||||
let mut entries = values.into_iter().collect::<Vec<_>>();
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
Value::Object(
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key, truncate_guardian_action_value(value)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: popup
|
||||
---
|
||||
Experimental features
|
||||
Toggle experimental features. Changes are saved to config.toml.
|
||||
|
||||
› [ ] JavaScript REPL Enable a persistent Node-backed JavaScript REPL for interactive website debugging
|
||||
and other inline JavaScript execution capabilities. Requires Node >= v22.22.0
|
||||
installed.
|
||||
[ ] Bubblewrap sandbox Try the new linux sandbox based on bubblewrap.
|
||||
[ ] Multi-agents Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.
|
||||
[ ] Apps Use a connected ChatGPT App using "$". Install Apps via /apps command. Restart
|
||||
Codex after enabling.
|
||||
[ ] Guardian approvals Let a guardian subagent review `on-request` approval prompts instead of showing
|
||||
them to you, including sandbox escapes and blocked network access.
|
||||
[ ] Prevent sleep while running Keep your computer awake while Codex is running a thread.
|
||||
|
||||
Press space to select or enter to save for next conversation
|
||||
@@ -6949,7 +6949,12 @@ async fn experimental_popup_includes_guardian_approval() {
|
||||
chat.open_experimental_popup();
|
||||
|
||||
let popup = render_bottom_popup(&chat, 120);
|
||||
assert_snapshot!("experimental_popup_includes_guardian_approval", popup);
|
||||
let snapshot_name = if cfg!(target_os = "linux") {
|
||||
"experimental_popup_includes_guardian_approval_linux"
|
||||
} else {
|
||||
"experimental_popup_includes_guardian_approval"
|
||||
};
|
||||
assert_snapshot!(snapshot_name, popup);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user