codex-relay 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Codex Relay runs a local bridge server for the Codex Relay mobile app. Keep Codex on your computer, then use your phone to pair with that local session, send prompts, watch streamed output, and respond to approval requests.
4
4
 
5
+ Codex Relay is an independent project. It is not affiliated with, endorsed by, or sponsored by OpenAI or the OpenAI Codex team.
6
+
5
7
  ## Requirements
6
8
 
7
9
  - Node.js 22.14 or newer
@@ -145,3 +147,11 @@ kill -TERM <pid>
145
147
  ```
146
148
 
147
149
  If the mobile app cannot connect, confirm that the phone can reach the printed `Mobile:` URL and that the chosen port is not blocked by a firewall.
150
+
151
+ Connection checklist:
152
+
153
+ - Are the phone and computer on the same Wi-Fi or LAN?
154
+ - If keeping the same network is difficult, are both devices connected through Tailscale or another reachable private network?
155
+ - Can the phone open the exact `Mobile:` URL printed by the relay?
156
+ - Does the computer firewall allow inbound traffic on the relay port, usually `8787`?
157
+ - If the printed URL is not reachable, did you set `CODEX_RELAY_PUBLIC_URL` to a reachable LAN, Tailscale, or tunnel URL?
@@ -1,2 +1,2 @@
1
- import { $ as SandboxModeSchema, A as PairResponseSchema, At as WorkspaceTerminalResizeRequestSchema, B as QueuedThreadInputSchema, Bt as promptSkillMentionLabel, C as ListQueuedThreadInputsResponseSchema, Ct as WorkspaceMarkdownPreviewTargetSchema, D as ListWorkspaceFilesResponseSchema, Dt as WorkspaceTerminalInputRequestSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceSelectionRequestSchema, F as PromptAttachmentSummarySchema, Ft as createOpenApiDocument, G as ResolveApprovalRequestSchema, H as RateLimitWindowSchema, Ht as promptSkillMentionTextCandidates, I as PromptContextInputSchema, It as isPromptSkillMarkdownMention, J as RunThreadResponseSchema, K as ResolveApprovalResponseSchema, L as PromptContextSchema, Lt as normalizePromptContext, M as PendingInputRequestQuestionSchema, Mt as WorkspaceTerminalStartRequestSchema, N as PendingInputRequestSchema, Nt as apiPaths, O as PairEncryptedPayloadSchema, Ot as WorkspaceTerminalOutputChunkSchema, P as PromptAttachmentSchema, Pt as chatMessageDetailsFromPromptContext, Q as RuntimePreferencesSchema, R as PromptSkillSchema, Rt as promptMarkdownWithSkills, S as ListModelsResponseSchema, St as WorkspaceGitActionResponseSchema, T as ListThreadsResponseSchema, Tt as WorkspacePreviewTabSchema, U as RateLimitsResponseSchema, Ut as stripPromptSkillMentions, V as RateLimitBucketSchema, Vt as promptSkillMentionMarkdown, W as ReasoningEffortSchema, X as RuntimePreferencesByWorkspacePathSchema, Y as RuntimeModeSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as WebPreviewTargetSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WorkspaceFileContentResponseSchema, c as ChatMessageRoleSchema, ct as ThreadMessageDetailResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadSummarySchema, et as StatusResponseSchema, f as CodexModelSchema, ft as UpdateRuntimePreferencesRequestSchema, g as CreateThreadResponseSchema, gt as WORKSPACE_PREVIEW_TAB_VALUES, h as CreateThreadRequestSchema, ht as WORKSPACE_PREVIEW_OPEN_PROTOCOL, i as ApprovalModeSchema, it as ThreadCollaborationModeSchema, j as PendingInputRequestOptionSchema, jt as WorkspaceTerminalSessionResponseSchema, k as PairRequestSchema, kt as WorkspaceTerminalOutputResponseSchema, l as ChatMessageSchema, lt as ThreadRunOptionsSchema, m as ContextWindowUsageSchema, mt as VersionResponseSchema, n as AgentSkillSourceSchema, nt as StreamThreadRunRequestSchema, o as ChatMessageKindSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as UpdateWorkspaceFileContentRequestSchema, q as RunThreadRequestSchema, r as ApprovalDecisionSchema, rt as SubmitThreadInputResponseSchema, s as ChatMessagePromptDetailsSchema, st as ThreadMessageDetailFieldSchema, t as AgentSkillSchema, tt as StreamThreadRunEventSchema, u as ChatMessageStateSchema, ut as ThreadStateSchema, v as ErrorResponseSchema, vt as WorkspaceChangesResponseSchema, w as ListSkillsResponseSchema, wt as WorkspacePreviewNavigationRequestSchema, x as IsoDateTimeSchema, xt as WorkspaceFileMentionSchema, y as ImageAttachmentUploadResponseSchema, yt as WorkspaceDirectoryEntrySchema, z as QueuedThreadInputActionResponseSchema, zt as promptSkillDisplayName } from "./api-schema2.js";
2
- export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, stripPromptSkillMentions };
1
+ import { $ as SandboxModeSchema, A as PairResponseSchema, At as WorkspaceSelectionRequestSchema, B as QueuedThreadInputSchema, Bt as isPromptSkillMarkdownMention, C as ListQueuedThreadInputsResponseSchema, Ct as WorkspaceDirectoryEntrySchema, D as ListWorkspaceFilesResponseSchema, Dt as WorkspaceMarkdownPreviewTargetSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, F as PromptAttachmentSummarySchema, Ft as WorkspaceTerminalSessionResponseSchema, G as ResolveApprovalRequestSchema, Gt as promptSkillMentionMarkdown, H as RateLimitWindowSchema, Ht as promptMarkdownWithSkills, I as PromptContextInputSchema, It as WorkspaceTerminalStartRequestSchema, J as RunThreadResponseSchema, K as ResolveApprovalResponseSchema, Kt as promptSkillMentionTextCandidates, L as PromptContextSchema, Lt as apiPaths, M as PendingInputRequestQuestionSchema, Mt as WorkspaceTerminalOutputChunkSchema, N as PendingInputRequestSchema, Nt as WorkspaceTerminalOutputResponseSchema, O as PairEncryptedPayloadSchema, Ot as WorkspacePreviewNavigationRequestSchema, P as PromptAttachmentSchema, Pt as WorkspaceTerminalResizeRequestSchema, Q as RuntimePreferencesSchema, R as PromptSkillSchema, Rt as chatMessageDetailsFromPromptContext, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, Tt as WorkspaceFileMentionSchema, U as RateLimitsResponseSchema, Ut as promptSkillDisplayName, V as RateLimitBucketSchema, Vt as normalizePromptContext, W as ReasoningEffortSchema, Wt as promptSkillMentionLabel, X as RuntimePreferencesByWorkspacePathSchema, Y as RuntimeModeSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WORKSPACE_PREVIEW_TAB_VALUES, c as ChatMessageRoleSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, f as CodexModelSchema, ft as ThreadRunOptionsSchema, g as CreateThreadResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, i as ApprovalModeSchema, it as ThreadCollaborationModeSchema, j as PendingInputRequestOptionSchema, jt as WorkspaceTerminalInputRequestSchema, k as PairRequestSchema, kt as WorkspacePreviewTabSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, n as AgentSkillSourceSchema, nt as StreamThreadRunRequestSchema, o as ChatMessageKindSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadStateSchema, q as RunThreadRequestSchema, qt as stripPromptSkillMentions, r as ApprovalDecisionSchema, rt as SubmitThreadInputResponseSchema, s as ChatMessagePromptDetailsSchema, st as ThreadGoalResponseSchema, t as AgentSkillSchema, tt as StreamThreadRunEventSchema, u as ChatMessageStateSchema, ut as ThreadMessageDetailFieldSchema, v as ErrorResponseSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, x as IsoDateTimeSchema, xt as WebPreviewTargetSchema, y as ImageAttachmentUploadResponseSchema, yt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, z as QueuedThreadInputActionResponseSchema, zt as createOpenApiDocument } from "./api-schema2.js";
2
+ export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadGoalResponseSchema, ThreadGoalSchema, ThreadGoalStatusSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateThreadGoalRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, stripPromptSkillMentions };
@@ -57,6 +57,14 @@ const ReasoningEffortSchema = z.enum([
57
57
  "xhigh"
58
58
  ]);
59
59
  const ThreadCollaborationModeSchema = z.enum(["default", "plan"]);
60
+ const ThreadGoalStatusSchema = z.enum([
61
+ "active",
62
+ "paused",
63
+ "blocked",
64
+ "usageLimited",
65
+ "budgetLimited",
66
+ "complete"
67
+ ]);
60
68
  const VersionResponseSchema = z.object({
61
69
  ok: z.boolean(),
62
70
  service: z.literal("codex-relay-server"),
@@ -145,6 +153,16 @@ const ThreadContextWindowResponseSchema = z.object({
145
153
  usage: ContextWindowUsageSchema.nullable(),
146
154
  rolloutPath: z.string().nullable().optional()
147
155
  });
156
+ const ThreadGoalSchema = z.object({
157
+ threadId: z.string().min(1),
158
+ objective: z.string().trim().min(1),
159
+ status: ThreadGoalStatusSchema,
160
+ tokenBudget: z.number().int().positive().nullable(),
161
+ tokensUsed: z.number().int().nonnegative(),
162
+ timeUsedSeconds: z.number().int().nonnegative(),
163
+ createdAt: IsoDateTimeSchema,
164
+ updatedAt: IsoDateTimeSchema
165
+ });
148
166
  const PromptAttachmentBaseSchema = z.object({
149
167
  type: z.literal("image"),
150
168
  mimeType: z.string().trim().startsWith("image/").optional(),
@@ -247,7 +265,8 @@ const ThreadSummarySchema = z.object({
247
265
  lastActivityAt: IsoDateTimeSchema.optional(),
248
266
  lastPrompt: z.string().optional(),
249
267
  lastResult: z.string().optional(),
250
- lastError: z.string().optional()
268
+ lastError: z.string().optional(),
269
+ goal: ThreadGoalSchema.nullable().optional()
251
270
  });
252
271
  const StatusResponseSchema = z.object({
253
272
  ok: z.boolean(),
@@ -473,6 +492,15 @@ const QueuedThreadInputActionResponseSchema = z.object({
473
492
  queueLength: z.number().int().nonnegative(),
474
493
  thread: ThreadSummarySchema
475
494
  });
495
+ const UpdateThreadGoalRequestSchema = z.object({
496
+ objective: z.string().trim().min(1).optional(),
497
+ status: ThreadGoalStatusSchema.optional(),
498
+ tokenBudget: z.number().int().positive().nullable().optional()
499
+ }).refine((input) => input.objective || input.status || input.tokenBudget !== void 0, { message: "At least one goal field is required." });
500
+ const ThreadGoalResponseSchema = z.object({
501
+ goal: ThreadGoalSchema.nullable(),
502
+ thread: ThreadSummarySchema
503
+ });
476
504
  const InterruptThreadRunResponseSchema = z.object({ thread: ThreadSummarySchema });
477
505
  const ListQueuedThreadInputsResponseSchema = z.object({
478
506
  inputs: z.array(QueuedThreadInputSchema),
@@ -533,6 +561,11 @@ const StreamThreadRunEventSchema = z.discriminatedUnion("type", [
533
561
  type: z.literal("thread.state.changed"),
534
562
  thread: ThreadSummarySchema
535
563
  }),
564
+ z.object({
565
+ type: z.literal("thread.goal.updated"),
566
+ thread: ThreadSummarySchema,
567
+ goal: ThreadGoalSchema.nullable()
568
+ }),
536
569
  z.object({
537
570
  type: z.literal("thread.error"),
538
571
  thread: ThreadSummarySchema.optional(),
@@ -697,6 +730,7 @@ const apiPaths = {
697
730
  thread: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}`,
698
731
  threadArchive: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}`,
699
732
  threadContextWindow: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/context-window`,
733
+ threadGoal: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/goal`,
700
734
  threadMessageDetail: (threadId, messageId, field) => `/v1/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/details/${encodeURIComponent(field)}`,
701
735
  approval: (approvalId) => `/v1/approvals/${encodeURIComponent(approvalId)}`,
702
736
  threadInput: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/input`,
@@ -711,7 +745,7 @@ function createOpenApiDocument() {
711
745
  openapi: "3.1.0",
712
746
  info: {
713
747
  title: "Codex Relay Local Codex API",
714
- version: "0.1.0"
748
+ version: "1.2.0"
715
749
  },
716
750
  paths: {
717
751
  "/version": { get: {
@@ -838,6 +872,36 @@ function createOpenApiDocument() {
838
872
  "409": jsonResponse("ErrorResponse")
839
873
  }
840
874
  } },
875
+ "/v1/threads/{threadId}/goal": {
876
+ get: {
877
+ summary: "Read the active goal for a Codex app-server thread",
878
+ responses: {
879
+ "200": jsonResponse("ThreadGoalResponse"),
880
+ "404": jsonResponse("ErrorResponse"),
881
+ "502": jsonResponse("ErrorResponse")
882
+ }
883
+ },
884
+ post: {
885
+ summary: "Update the active goal for a Codex app-server thread",
886
+ requestBody: jsonRequest("UpdateThreadGoalRequest"),
887
+ responses: {
888
+ "200": jsonResponse("ThreadGoalResponse"),
889
+ "400": jsonResponse("ErrorResponse"),
890
+ "404": jsonResponse("ErrorResponse"),
891
+ "409": jsonResponse("ErrorResponse"),
892
+ "502": jsonResponse("ErrorResponse")
893
+ }
894
+ },
895
+ delete: {
896
+ summary: "Clear the active goal for a Codex app-server thread",
897
+ responses: {
898
+ "200": jsonResponse("ThreadGoalResponse"),
899
+ "404": jsonResponse("ErrorResponse"),
900
+ "409": jsonResponse("ErrorResponse"),
901
+ "502": jsonResponse("ErrorResponse")
902
+ }
903
+ }
904
+ },
841
905
  "/v1/threads/{threadId}/input": {
842
906
  post: {
843
907
  summary: "Submit input to an already-running Codex thread",
@@ -970,7 +1034,71 @@ function createOpenApiDocument() {
970
1034
  },
971
1035
  lastPrompt: { type: "string" },
972
1036
  lastResult: { type: "string" },
973
- lastError: { type: "string" }
1037
+ lastError: { type: "string" },
1038
+ goal: { anyOf: [{ $ref: "#/components/schemas/ThreadGoal" }, { type: "null" }] }
1039
+ }
1040
+ },
1041
+ ThreadGoal: {
1042
+ type: "object",
1043
+ required: [
1044
+ "threadId",
1045
+ "objective",
1046
+ "status",
1047
+ "tokenBudget",
1048
+ "tokensUsed",
1049
+ "timeUsedSeconds",
1050
+ "createdAt",
1051
+ "updatedAt"
1052
+ ],
1053
+ properties: {
1054
+ threadId: { type: "string" },
1055
+ objective: { type: "string" },
1056
+ status: {
1057
+ type: "string",
1058
+ enum: ThreadGoalStatusSchema.options
1059
+ },
1060
+ tokenBudget: { anyOf: [{
1061
+ type: "integer",
1062
+ minimum: 1
1063
+ }, { type: "null" }] },
1064
+ tokensUsed: {
1065
+ type: "integer",
1066
+ minimum: 0
1067
+ },
1068
+ timeUsedSeconds: {
1069
+ type: "integer",
1070
+ minimum: 0
1071
+ },
1072
+ createdAt: {
1073
+ type: "string",
1074
+ format: "date-time"
1075
+ },
1076
+ updatedAt: {
1077
+ type: "string",
1078
+ format: "date-time"
1079
+ }
1080
+ }
1081
+ },
1082
+ UpdateThreadGoalRequest: {
1083
+ type: "object",
1084
+ properties: {
1085
+ objective: { type: "string" },
1086
+ status: {
1087
+ type: "string",
1088
+ enum: ThreadGoalStatusSchema.options
1089
+ },
1090
+ tokenBudget: { anyOf: [{
1091
+ type: "integer",
1092
+ minimum: 1
1093
+ }, { type: "null" }] }
1094
+ }
1095
+ },
1096
+ ThreadGoalResponse: {
1097
+ type: "object",
1098
+ required: ["goal", "thread"],
1099
+ properties: {
1100
+ goal: { anyOf: [{ $ref: "#/components/schemas/ThreadGoal" }, { type: "null" }] },
1101
+ thread: { $ref: "#/components/schemas/ThreadSummary" }
974
1102
  }
975
1103
  },
976
1104
  StatusResponse: {
@@ -1561,4 +1689,4 @@ function jsonResponse(schemaName) {
1561
1689
  };
1562
1690
  }
1563
1691
  //#endregion
1564
- export { SandboxModeSchema as $, PairResponseSchema as A, WorkspaceTerminalResizeRequestSchema as At, QueuedThreadInputSchema as B, promptSkillMentionLabel as Bt, ListQueuedThreadInputsResponseSchema as C, WorkspaceMarkdownPreviewTargetSchema as Ct, ListWorkspaceFilesResponseSchema as D, WorkspaceTerminalInputRequestSchema as Dt, ListWorkspaceDirectoriesResponseSchema as E, WorkspaceSelectionRequestSchema as Et, PromptAttachmentSummarySchema as F, createOpenApiDocument as Ft, ResolveApprovalRequestSchema as G, RateLimitWindowSchema as H, promptSkillMentionTextCandidates as Ht, PromptContextInputSchema as I, isPromptSkillMarkdownMention as It, RunThreadResponseSchema as J, ResolveApprovalResponseSchema as K, PromptContextSchema as L, normalizePromptContext as Lt, PendingInputRequestQuestionSchema as M, WorkspaceTerminalStartRequestSchema as Mt, PendingInputRequestSchema as N, apiPaths as Nt, PairEncryptedPayloadSchema as O, WorkspaceTerminalOutputChunkSchema as Ot, PromptAttachmentSchema as P, chatMessageDetailsFromPromptContext as Pt, RuntimePreferencesSchema as Q, PromptSkillSchema as R, promptMarkdownWithSkills as Rt, ListModelsResponseSchema as S, WorkspaceGitActionResponseSchema as St, ListThreadsResponseSchema as T, WorkspacePreviewTabSchema as Tt, RateLimitsResponseSchema as U, stripPromptSkillMentions as Ut, RateLimitBucketSchema as V, promptSkillMentionMarkdown as Vt, ReasoningEffortSchema as W, RuntimePreferencesByWorkspacePathSchema as X, RuntimeModeSchema as Y, RuntimePreferencesResponseSchema as Z, EncryptedPayloadSchema as _, WebPreviewTargetSchema as _t, ArchiveThreadResponseSchema as a, ThreadContextWindowResponseSchema as at, InterruptThreadRunResponseSchema as b, WorkspaceFileContentResponseSchema as bt, ChatMessageRoleSchema as c, ThreadMessageDetailResponseSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, ThreadSummarySchema as dt, StatusResponseSchema as et, CodexModelSchema as f, UpdateRuntimePreferencesRequestSchema as ft, CreateThreadResponseSchema as g, WORKSPACE_PREVIEW_TAB_VALUES as gt, CreateThreadRequestSchema as h, WORKSPACE_PREVIEW_OPEN_PROTOCOL as ht, ApprovalModeSchema as i, ThreadCollaborationModeSchema as it, PendingInputRequestOptionSchema as j, WorkspaceTerminalSessionResponseSchema as jt, PairRequestSchema as k, WorkspaceTerminalOutputResponseSchema as kt, ChatMessageSchema as l, ThreadRunOptionsSchema as lt, ContextWindowUsageSchema as m, VersionResponseSchema as mt, AgentSkillSourceSchema as n, StreamThreadRunRequestSchema as nt, ChatMessageKindSchema as o, ThreadDetailResponseSchema as ot, CommitPushWorkspaceRequestSchema as p, UpdateWorkspaceFileContentRequestSchema as pt, RunThreadRequestSchema as q, ApprovalDecisionSchema as r, SubmitThreadInputResponseSchema as rt, ChatMessagePromptDetailsSchema as s, ThreadMessageDetailFieldSchema as st, AgentSkillSchema as t, StreamThreadRunEventSchema as tt, ChatMessageStateSchema as u, ThreadStateSchema as ut, ErrorResponseSchema as v, WorkspaceChangesResponseSchema as vt, ListSkillsResponseSchema as w, WorkspacePreviewNavigationRequestSchema as wt, IsoDateTimeSchema as x, WorkspaceFileMentionSchema as xt, ImageAttachmentUploadResponseSchema as y, WorkspaceDirectoryEntrySchema as yt, QueuedThreadInputActionResponseSchema as z, promptSkillDisplayName as zt };
1692
+ export { SandboxModeSchema as $, PairResponseSchema as A, WorkspaceSelectionRequestSchema as At, QueuedThreadInputSchema as B, isPromptSkillMarkdownMention as Bt, ListQueuedThreadInputsResponseSchema as C, WorkspaceDirectoryEntrySchema as Ct, ListWorkspaceFilesResponseSchema as D, WorkspaceMarkdownPreviewTargetSchema as Dt, ListWorkspaceDirectoriesResponseSchema as E, WorkspaceGitActionResponseSchema as Et, PromptAttachmentSummarySchema as F, WorkspaceTerminalSessionResponseSchema as Ft, ResolveApprovalRequestSchema as G, promptSkillMentionMarkdown as Gt, RateLimitWindowSchema as H, promptMarkdownWithSkills as Ht, PromptContextInputSchema as I, WorkspaceTerminalStartRequestSchema as It, RunThreadResponseSchema as J, ResolveApprovalResponseSchema as K, promptSkillMentionTextCandidates as Kt, PromptContextSchema as L, apiPaths as Lt, PendingInputRequestQuestionSchema as M, WorkspaceTerminalOutputChunkSchema as Mt, PendingInputRequestSchema as N, WorkspaceTerminalOutputResponseSchema as Nt, PairEncryptedPayloadSchema as O, WorkspacePreviewNavigationRequestSchema as Ot, PromptAttachmentSchema as P, WorkspaceTerminalResizeRequestSchema as Pt, RuntimePreferencesSchema as Q, PromptSkillSchema as R, chatMessageDetailsFromPromptContext as Rt, ListModelsResponseSchema as S, WorkspaceChangesResponseSchema as St, ListThreadsResponseSchema as T, WorkspaceFileMentionSchema as Tt, RateLimitsResponseSchema as U, promptSkillDisplayName as Ut, RateLimitBucketSchema as V, normalizePromptContext as Vt, ReasoningEffortSchema as W, promptSkillMentionLabel as Wt, RuntimePreferencesByWorkspacePathSchema as X, RuntimeModeSchema as Y, RuntimePreferencesResponseSchema as Z, EncryptedPayloadSchema as _, UpdateWorkspaceFileContentRequestSchema as _t, ArchiveThreadResponseSchema as a, ThreadContextWindowResponseSchema as at, InterruptThreadRunResponseSchema as b, WORKSPACE_PREVIEW_TAB_VALUES as bt, ChatMessageRoleSchema as c, ThreadGoalSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, ThreadMessageDetailResponseSchema as dt, StatusResponseSchema as et, CodexModelSchema as f, ThreadRunOptionsSchema as ft, CreateThreadResponseSchema as g, UpdateThreadGoalRequestSchema as gt, CreateThreadRequestSchema as h, UpdateRuntimePreferencesRequestSchema as ht, ApprovalModeSchema as i, ThreadCollaborationModeSchema as it, PendingInputRequestOptionSchema as j, WorkspaceTerminalInputRequestSchema as jt, PairRequestSchema as k, WorkspacePreviewTabSchema as kt, ChatMessageSchema as l, ThreadGoalStatusSchema as lt, ContextWindowUsageSchema as m, ThreadSummarySchema as mt, AgentSkillSourceSchema as n, StreamThreadRunRequestSchema as nt, ChatMessageKindSchema as o, ThreadDetailResponseSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadStateSchema as pt, RunThreadRequestSchema as q, stripPromptSkillMentions as qt, ApprovalDecisionSchema as r, SubmitThreadInputResponseSchema as rt, ChatMessagePromptDetailsSchema as s, ThreadGoalResponseSchema as st, AgentSkillSchema as t, StreamThreadRunEventSchema as tt, ChatMessageStateSchema as u, ThreadMessageDetailFieldSchema as ut, ErrorResponseSchema as v, VersionResponseSchema as vt, ListSkillsResponseSchema as w, WorkspaceFileContentResponseSchema as wt, IsoDateTimeSchema as x, WebPreviewTargetSchema as xt, ImageAttachmentUploadResponseSchema as y, WORKSPACE_PREVIEW_OPEN_PROTOCOL as yt, QueuedThreadInputActionResponseSchema as z, createOpenApiDocument as zt };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Nt as apiPaths } from "./api-schema2.js";
2
+ import { Lt as apiPaths } from "./api-schema2.js";
3
3
  import { n as codexRelayHome, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
4
4
  import { Command } from "@commander-js/extra-typings";
5
5
  import qrcode from "qrcode-terminal";
package/dist/src.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as PairResponseSchema, At as WorkspaceTerminalResizeRequestSchema, C as ListQueuedThreadInputsResponseSchema, D as ListWorkspaceFilesResponseSchema, Dt as WorkspaceTerminalInputRequestSchema, E as ListWorkspaceDirectoriesResponseSchema, Ft as createOpenApiDocument, G as ResolveApprovalRequestSchema, K as ResolveApprovalResponseSchema, Lt as normalizePromptContext, Mt as WorkspaceTerminalStartRequestSchema, Nt as apiPaths, Pt as chatMessageDetailsFromPromptContext, Q as RuntimePreferencesSchema, Rt as promptMarkdownWithSkills, S as ListModelsResponseSchema, St as WorkspaceGitActionResponseSchema, T as ListThreadsResponseSchema, U as RateLimitsResponseSchema, Ut as stripPromptSkillMentions, X as RuntimePreferencesByWorkspacePathSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WorkspaceFileContentResponseSchema, ct as ThreadMessageDetailResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadSummarySchema, et as StatusResponseSchema, ft as UpdateRuntimePreferencesRequestSchema, h as CreateThreadRequestSchema, jt as WorkspaceTerminalSessionResponseSchema, k as PairRequestSchema, kt as WorkspaceTerminalOutputResponseSchema, l as ChatMessageSchema, m as ContextWindowUsageSchema, mt as VersionResponseSchema, nt as StreamThreadRunRequestSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as UpdateWorkspaceFileContentRequestSchema, q as RunThreadRequestSchema, rt as SubmitThreadInputResponseSchema, st as ThreadMessageDetailFieldSchema, tt as StreamThreadRunEventSchema, vt as WorkspaceChangesResponseSchema, w as ListSkillsResponseSchema, y as ImageAttachmentUploadResponseSchema, z as QueuedThreadInputActionResponseSchema } from "./api-schema2.js";
1
+ import { A as PairResponseSchema, C as ListQueuedThreadInputsResponseSchema, D as ListWorkspaceFilesResponseSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, Ft as WorkspaceTerminalSessionResponseSchema, G as ResolveApprovalRequestSchema, Ht as promptMarkdownWithSkills, It as WorkspaceTerminalStartRequestSchema, K as ResolveApprovalResponseSchema, Lt as apiPaths, Nt as WorkspaceTerminalOutputResponseSchema, Pt as WorkspaceTerminalResizeRequestSchema, Q as RuntimePreferencesSchema, Rt as chatMessageDetailsFromPromptContext, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, U as RateLimitsResponseSchema, Vt as normalizePromptContext, X as RuntimePreferencesByWorkspacePathSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, jt as WorkspaceTerminalInputRequestSchema, k as PairRequestSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, nt as StreamThreadRunRequestSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, q as RunThreadRequestSchema, qt as stripPromptSkillMentions, rt as SubmitThreadInputResponseSchema, st as ThreadGoalResponseSchema, tt as StreamThreadRunEventSchema, ut as ThreadMessageDetailFieldSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, y as ImageAttachmentUploadResponseSchema, z as QueuedThreadInputActionResponseSchema, zt as createOpenApiDocument } from "./api-schema2.js";
2
2
  import { a as getConnectUrlCandidates, i as createPairingQrPayload, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
3
3
  import { createRequire } from "node:module";
4
4
  import qrcode from "qrcode-terminal";
@@ -73,6 +73,15 @@ var CodexAppServerClient = class {
73
73
  async archiveThread(params) {
74
74
  await this.request("thread/archive", params);
75
75
  }
76
+ async getThreadGoal(params) {
77
+ return (await this.request("thread/goal/get", params)).goal;
78
+ }
79
+ async setThreadGoal(params) {
80
+ return (await this.request("thread/goal/set", params)).goal;
81
+ }
82
+ async clearThreadGoal(params) {
83
+ await this.request("thread/goal/clear", params);
84
+ }
76
85
  onNotification(handler) {
77
86
  this.notificationHandlers.add(handler);
78
87
  return () => this.notificationHandlers.delete(handler);
@@ -155,7 +164,7 @@ var CodexAppServerClient = class {
155
164
  clientInfo: {
156
165
  name: "codex-relay",
157
166
  title: "Codex Relay Mobile Server",
158
- version: "0.1.0"
167
+ version: "1.2.0"
159
168
  },
160
169
  capabilities: { experimentalApi: true }
161
170
  }).then(() => void 0);
@@ -822,6 +831,16 @@ const defaultWebPreviewPorts = [
822
831
  19006
823
832
  ];
824
833
  const PairApproveRequestSchema = z.object({ approvalCode: z.string().trim().min(1) });
834
+ const AppServerThreadGoalPayloadSchema = z.object({
835
+ threadId: z.string().min(1),
836
+ objective: z.string().trim().min(1),
837
+ status: ThreadGoalStatusSchema,
838
+ tokenBudget: z.number().int().positive().nullable(),
839
+ tokensUsed: z.number().int().nonnegative(),
840
+ timeUsedSeconds: z.number().int().nonnegative(),
841
+ createdAt: z.number(),
842
+ updatedAt: z.number()
843
+ });
825
844
  const maxResolvedApprovals = 100;
826
845
  const maxWorkspaceTerminalOutputChunks = 2e3;
827
846
  function createApp(options = {}) {
@@ -1570,6 +1589,72 @@ function createApp(options = {}) {
1570
1589
  usage: result.usage
1571
1590
  }));
1572
1591
  });
1592
+ app.get("/v1/threads/:threadId/goal", async (c) => {
1593
+ const threadId = c.req.param("threadId");
1594
+ if (!appServer) {
1595
+ const thread = threads.get(threadId);
1596
+ if (!thread) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
1597
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, ThreadGoalResponseSchema.parse({
1598
+ goal: thread.goal ?? null,
1599
+ thread
1600
+ }));
1601
+ }
1602
+ try {
1603
+ const thread = rememberAppServerThread(threads, await appServer.readThread(threadId, { includeTurns: false }));
1604
+ const goal = mapAppServerThreadGoal(await appServer.getThreadGoal({ threadId }));
1605
+ const threadWithGoal = rememberThreadGoal(threads, thread, goal);
1606
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, ThreadGoalResponseSchema.parse({
1607
+ goal,
1608
+ thread: threadWithGoal
1609
+ }));
1610
+ } catch (error) {
1611
+ const message = errorMessage(error);
1612
+ const status = /not found|no rollout found/i.test(message) ? 404 : 502;
1613
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(status === 404 ? "not_found" : "goal_unavailable", message), status);
1614
+ }
1615
+ });
1616
+ app.post("/v1/threads/:threadId/goal", async (c) => {
1617
+ const threadId = c.req.param("threadId");
1618
+ if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Thread goals require the Codex app-server."), 409);
1619
+ const parsed = await parseRequestJson(c, options.pairing, secureSessionsByTokenHash, UpdateThreadGoalRequestSchema);
1620
+ if (!parsed.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, validationError(parsed.error), 400);
1621
+ try {
1622
+ const body = parsed.data;
1623
+ const thread = rememberAppServerThread(threads, await appServer.readThread(threadId, { includeTurns: false }));
1624
+ const goal = mapAppServerThreadGoal(await appServer.setThreadGoal({
1625
+ threadId,
1626
+ objective: body.objective,
1627
+ status: body.status,
1628
+ tokenBudget: body.tokenBudget
1629
+ }));
1630
+ const threadWithGoal = rememberThreadGoal(threads, thread, goal);
1631
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, ThreadGoalResponseSchema.parse({
1632
+ goal,
1633
+ thread: threadWithGoal
1634
+ }));
1635
+ } catch (error) {
1636
+ const message = errorMessage(error);
1637
+ const status = /not found|no rollout found/i.test(message) ? 404 : 502;
1638
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(status === 404 ? "not_found" : "goal_unavailable", message), status);
1639
+ }
1640
+ });
1641
+ app.delete("/v1/threads/:threadId/goal", async (c) => {
1642
+ const threadId = c.req.param("threadId");
1643
+ if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Thread goals require the Codex app-server."), 409);
1644
+ try {
1645
+ const thread = rememberAppServerThread(threads, await appServer.readThread(threadId, { includeTurns: false }));
1646
+ await appServer.clearThreadGoal({ threadId });
1647
+ const threadWithoutGoal = rememberThreadGoal(threads, thread, null);
1648
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, ThreadGoalResponseSchema.parse({
1649
+ goal: null,
1650
+ thread: threadWithoutGoal
1651
+ }));
1652
+ } catch (error) {
1653
+ const message = errorMessage(error);
1654
+ const status = /not found|no rollout found/i.test(message) ? 404 : 502;
1655
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(status === 404 ? "not_found" : "goal_unavailable", message), status);
1656
+ }
1657
+ });
1573
1658
  app.get("/openapi.json", (c) => secureJson(c, options.pairing, secureSessionsByTokenHash, createOpenApiDocument()));
1574
1659
  app.post("/v1/approvals/:approvalId", async (c) => {
1575
1660
  const approvalId = c.req.param("approvalId");
@@ -2229,16 +2314,17 @@ async function runPromptStreamed(input) {
2229
2314
  if (kind === "error") throw new Error(text ?? "Codex run failed.");
2230
2315
  if (!text) continue;
2231
2316
  if (kind === "assistant") {
2232
- assistantMessage = appendMessageDelta(input.messagesByThreadId, activeThreadId, assistantMessage.id, text);
2317
+ const assistantPatch = appendMessageDelta(input.messagesByThreadId, activeThreadId, assistantMessage.id, text);
2318
+ assistantMessage = assistantPatch.message;
2233
2319
  updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
2234
2320
  state: "running",
2235
2321
  lastResult: assistantMessage.content
2236
2322
  });
2237
- sendSse(input.controller, input.encoder, input.secureSession, {
2323
+ if (assistantPatch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
2238
2324
  type: "thread.message.delta",
2239
2325
  threadId: activeThreadId,
2240
2326
  messageId: assistantMessage.id,
2241
- delta: text
2327
+ delta: assistantPatch.delta
2242
2328
  });
2243
2329
  } else {
2244
2330
  const structured = structuredStreamMessage(kind, event, text);
@@ -2459,6 +2545,19 @@ async function streamRunningAppServerThread(input) {
2459
2545
  }
2460
2546
  return;
2461
2547
  }
2548
+ case "thread/goal/updated":
2549
+ case "thread/goal/cleared": {
2550
+ const goal = notification.method === "thread/goal/updated" ? mapAppServerThreadGoal(appServerThreadGoalFromParams(params)) : null;
2551
+ const currentThreadSummary = threadSummary ?? input.threads.get(input.threadId);
2552
+ if (!currentThreadSummary) return;
2553
+ threadSummary = rememberThreadGoal(input.threads, currentThreadSummary, goal);
2554
+ sendSse(input.controller, input.encoder, input.secureSession, {
2555
+ type: "thread.goal.updated",
2556
+ thread: threadSummary,
2557
+ goal
2558
+ });
2559
+ return;
2560
+ }
2462
2561
  case "turn/started":
2463
2562
  observedTurnActivity = true;
2464
2563
  activeTurnId = firstString(params, ["turnId"]) ?? turnIdFromParams(params);
@@ -2494,16 +2593,17 @@ async function streamRunningAppServerThread(input) {
2494
2593
  turnId: firstString(params, ["turnId"]) ?? activeTurnId
2495
2594
  });
2496
2595
  assistantMessageId = itemId;
2497
- const message = appendMessageDelta(input.messagesByThreadId, input.threadId, itemId, delta);
2596
+ const patch = appendMessageDelta(input.messagesByThreadId, input.threadId, itemId, delta);
2597
+ const message = patch.message;
2498
2598
  updateThread(input.threads, input.messagesByThreadId, input.threadId, {
2499
2599
  state: "running",
2500
2600
  lastResult: message.content
2501
2601
  });
2502
- sendSse(input.controller, input.encoder, input.secureSession, {
2602
+ if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
2503
2603
  type: "thread.message.delta",
2504
2604
  threadId: input.threadId,
2505
2605
  messageId: itemId,
2506
- delta
2606
+ delta: patch.delta
2507
2607
  });
2508
2608
  return;
2509
2609
  }
@@ -2697,6 +2797,17 @@ async function runAppServerPromptStreamed(input) {
2697
2797
  });
2698
2798
  return;
2699
2799
  }
2800
+ case "thread/goal/updated":
2801
+ case "thread/goal/cleared": {
2802
+ const goal = notification.method === "thread/goal/updated" ? mapAppServerThreadGoal(appServerThreadGoalFromParams(params)) : null;
2803
+ threadSummary = rememberThreadGoal(input.threads, threadSummary, goal);
2804
+ sendSse(input.controller, input.encoder, input.secureSession, {
2805
+ type: "thread.goal.updated",
2806
+ thread: threadSummary,
2807
+ goal
2808
+ });
2809
+ return;
2810
+ }
2700
2811
  case "turn/started":
2701
2812
  activeTurnId = firstString(params, ["turnId"]) ?? turnIdFromParams(params);
2702
2813
  if (activeTurnId) input.activeAppServerTurnIdsByThreadId.set(activeThreadId, activeTurnId);
@@ -2705,7 +2816,7 @@ async function runAppServerPromptStreamed(input) {
2705
2816
  case "item/completed": {
2706
2817
  const item = params?.item;
2707
2818
  if (!item || typeof item !== "object") return;
2708
- const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id, prompt);
2819
+ const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id, displayPrompt);
2709
2820
  if (canonicalUserMessage) {
2710
2821
  userMessage = canonicalUserMessage;
2711
2822
  return;
@@ -2737,17 +2848,18 @@ async function runAppServerPromptStreamed(input) {
2737
2848
  turnId: firstString(params, ["turnId"]) ?? activeTurnId
2738
2849
  });
2739
2850
  assistantMessageId = itemId;
2740
- const message = appendMessageDelta(input.messagesByThreadId, activeThreadId, itemId, delta);
2851
+ const patch = appendMessageDelta(input.messagesByThreadId, activeThreadId, itemId, delta);
2852
+ const message = patch.message;
2741
2853
  producedTurnOutput = true;
2742
2854
  updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
2743
2855
  state: "running",
2744
2856
  lastResult: message.content
2745
2857
  });
2746
- sendSse(input.controller, input.encoder, input.secureSession, {
2858
+ if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
2747
2859
  type: "thread.message.delta",
2748
2860
  threadId: activeThreadId,
2749
2861
  messageId: itemId,
2750
- delta
2862
+ delta: patch.delta
2751
2863
  });
2752
2864
  return;
2753
2865
  }
@@ -3269,8 +3381,9 @@ function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, local
3269
3381
  if (item.type !== "userMessage" || !("content" in item) || !Array.isArray(item.content)) return false;
3270
3382
  const localMessage = messagesByThreadId.get(threadId)?.find((message) => message.id === localMessageId);
3271
3383
  const skills = appServerUserMessageSkills(item);
3272
- const normalizedPrompt = stripPromptSkillMentions(prompt, skills);
3273
- return stripPromptSkillMentions(localMessage?.content ?? "", skills) === normalizedPrompt && stripPromptSkillMentions(appServerUserMessageText(item), skills) === normalizedPrompt;
3384
+ const normalizeContent = (content) => stripPromptSkillMentions(normalizeImageMessageContent(content), skills);
3385
+ const normalizedPrompt = normalizeContent(prompt);
3386
+ return normalizeContent(localMessage?.content ?? "") === normalizedPrompt && normalizeContent(appServerUserMessageText(item)) === normalizedPrompt;
3274
3387
  }
3275
3388
  function messageWithReplacementDetail(message, replacesMessageId) {
3276
3389
  return ChatMessageSchema.parse({
@@ -3526,10 +3639,18 @@ function isAppServerSkillInput(input) {
3526
3639
  }
3527
3640
  function appendMessageDelta(messagesByThreadId, threadId, messageId, delta) {
3528
3641
  const existing = messagesByThreadId.get(threadId)?.find((message) => message.id === messageId);
3529
- return updateMessage(messagesByThreadId, threadId, messageId, {
3530
- content: `${existing?.content ?? ""}${delta}`,
3531
- state: "streaming"
3532
- });
3642
+ const normalizedDelta = normalizeStreamDelta(existing?.content ?? "", delta);
3643
+ return {
3644
+ delta: normalizedDelta,
3645
+ message: updateMessage(messagesByThreadId, threadId, messageId, {
3646
+ content: `${existing?.content ?? ""}${normalizedDelta}`,
3647
+ state: "streaming"
3648
+ })
3649
+ };
3650
+ }
3651
+ function normalizeStreamDelta(existingContent, incomingDelta) {
3652
+ if (!existingContent || !incomingDelta.startsWith(existingContent)) return incomingDelta;
3653
+ return incomingDelta.slice(existingContent.length);
3533
3654
  }
3534
3655
  function markApprovalMessageResolved(messagesByThreadId, threadId, messageId, decision) {
3535
3656
  const message = (messagesByThreadId.get(threadId) ?? []).find((candidate) => candidate.id === messageId);
@@ -3932,12 +4053,33 @@ function rememberAppServerThread(threads, thread) {
3932
4053
  const mappedThread = mapAppServerThread(thread, existingThread?.messageCount);
3933
4054
  const threadWithLocalRuntime = ThreadSummarySchema.parse({
3934
4055
  ...mappedThread,
4056
+ goal: existingThread?.goal ?? mappedThread.goal,
3935
4057
  ...runtimeMetadataFromOptions(existingThread ?? {}),
3936
4058
  model: existingThread?.model ?? mappedThread.model
3937
4059
  });
3938
4060
  threads.set(threadWithLocalRuntime.id, threadWithLocalRuntime);
3939
4061
  return threadWithLocalRuntime;
3940
4062
  }
4063
+ function mapAppServerThreadGoal(goal) {
4064
+ if (!goal) return null;
4065
+ return ThreadGoalSchema.parse({
4066
+ ...goal,
4067
+ createdAt: fromUnixSeconds(goal.createdAt),
4068
+ updatedAt: fromUnixSeconds(goal.updatedAt)
4069
+ });
4070
+ }
4071
+ function appServerThreadGoalFromParams(params) {
4072
+ const parsed = AppServerThreadGoalPayloadSchema.safeParse(params?.goal);
4073
+ return parsed.success ? parsed.data : null;
4074
+ }
4075
+ function rememberThreadGoal(threads, thread, goal) {
4076
+ const next = ThreadSummarySchema.parse({
4077
+ ...thread,
4078
+ goal
4079
+ });
4080
+ threads.set(next.id, next);
4081
+ return next;
4082
+ }
3941
4083
  function preserveKnownRunningThreadState(thread, wasKnownRunning) {
3942
4084
  if (!wasKnownRunning || thread.state === "running") return thread;
3943
4085
  return ThreadSummarySchema.parse({
@@ -4092,6 +4234,7 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
4092
4234
  };
4093
4235
  const collected = [];
4094
4236
  const applyPatchInputs = /* @__PURE__ */ new Map();
4237
+ const handledApplyPatchCallIds = /* @__PURE__ */ new Set();
4095
4238
  const pendingApplyPatchChanges = [];
4096
4239
  const lines = readFileSync(rolloutPath, "utf8").split("\n");
4097
4240
  for (let index = 0; index < lines.length; index += 1) {
@@ -4102,7 +4245,7 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
4102
4245
  try {
4103
4246
  const record = JSON.parse(line);
4104
4247
  rememberRolloutApplyPatchInput(record, applyPatchInputs);
4105
- collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges);
4248
+ collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, handledApplyPatchCallIds, pendingApplyPatchChanges);
4106
4249
  if (isRolloutTaskComplete(record) && pendingApplyPatchChanges.length > 0) {
4107
4250
  collected.push(rolloutApplyPatchSummaryMessage(threadId, record, `rollout:${lineNumber}:apply_patch`, pendingApplyPatchChanges));
4108
4251
  pendingApplyPatchChanges.length = 0;
@@ -4111,6 +4254,11 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
4111
4254
  const message = rolloutRecordMessage(threadId, record, `rollout:${lineNumber}`, workspacePath);
4112
4255
  if (!message) continue;
4113
4256
  collected.push(message);
4257
+ const patchApplyEndCallId = rolloutPatchApplyEndCallId(record);
4258
+ if (patchApplyEndCallId) {
4259
+ handledApplyPatchCallIds.add(patchApplyEndCallId);
4260
+ pendingApplyPatchChanges.splice(0, pendingApplyPatchChanges.length, ...pendingApplyPatchChanges.filter((change) => change.callId !== patchApplyEndCallId));
4261
+ }
4114
4262
  } catch {}
4115
4263
  }
4116
4264
  return {
@@ -4172,7 +4320,7 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
4172
4320
  if (record.type === "event_msg" && payload.type === "patch_apply_end") {
4173
4321
  const changes = rolloutPatchApplyChanges(payload.changes, workspacePath);
4174
4322
  if (changes.length === 0) return;
4175
- const patchPreview = largeTextPreview(changes.map((change) => change.patch).filter((patch) => Boolean(patch)).join("\n"));
4323
+ const patchPreview = largeTextPreview(rolloutPatchPreview(changes));
4176
4324
  return ChatMessageSchema.parse({
4177
4325
  id: `${messageKey}:patch:${firstString(payload, ["call_id"]) ?? ""}`,
4178
4326
  threadId,
@@ -4182,7 +4330,7 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
4182
4330
  createdAt: timestamp,
4183
4331
  state: "completed",
4184
4332
  details: {
4185
- changes: changes.map(({ patch: _patch, ...change }) => change),
4333
+ changes: changes.map(publicRolloutPatchChange),
4186
4334
  patch: patchPreview?.text,
4187
4335
  patchOriginalLength: patchPreview?.originalLength,
4188
4336
  patchTruncated: patchPreview?.truncated
@@ -4250,15 +4398,16 @@ function rememberRolloutApplyPatchInput(record, applyPatchInputs) {
4250
4398
  const input = firstString(payload, ["input"]);
4251
4399
  if (callId && input) applyPatchInputs.set(callId, input);
4252
4400
  }
4253
- function collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges) {
4401
+ function collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, handledApplyPatchCallIds, pendingApplyPatchChanges) {
4254
4402
  const payload = record.payload;
4255
- if (record.type !== "response_item" || payload?.type !== "custom_tool_call_output" || !firstString(payload, ["call_id"])) return;
4403
+ const callId = firstString(payload, ["call_id"]);
4404
+ if (record.type !== "response_item" || payload?.type !== "custom_tool_call_output" || !callId || handledApplyPatchCallIds.has(callId)) return;
4256
4405
  const changes = rolloutApplyPatchOutputChanges(rolloutCustomToolOutputText(payload), workspacePath);
4257
4406
  if (changes.length === 0) return;
4258
- const callId = firstString(payload, ["call_id"]);
4259
4407
  const patch = callId ? applyPatchInputs.get(callId) : void 0;
4260
4408
  for (const change of changes) pendingApplyPatchChanges.push({
4261
4409
  ...change,
4410
+ callId,
4262
4411
  patch
4263
4412
  });
4264
4413
  }
@@ -4274,13 +4423,36 @@ function rolloutApplyPatchSummaryMessage(threadId, record, messageKey, pendingAp
4274
4423
  createdAt: timestamp,
4275
4424
  state: "completed",
4276
4425
  details: {
4277
- changes: pendingApplyPatchChanges.map(({ patch: _patch, ...change }) => change),
4426
+ changes: pendingApplyPatchChanges.map(publicRolloutPatchChange),
4278
4427
  patch: patchPreview?.text,
4279
4428
  patchOriginalLength: patchPreview?.originalLength,
4280
4429
  patchTruncated: patchPreview?.truncated
4281
4430
  }
4282
4431
  });
4283
4432
  }
4433
+ function rolloutPatchApplyEndCallId(record) {
4434
+ if (record.type !== "event_msg" || record.payload?.type !== "patch_apply_end") return;
4435
+ return firstString(record.payload, ["call_id"]);
4436
+ }
4437
+ function publicRolloutPatchChange(change) {
4438
+ const { callId: _callId, patch: _patch, ...publicChange } = change;
4439
+ return publicChange;
4440
+ }
4441
+ function rolloutPatchPreview(changes) {
4442
+ return changes.flatMap((change) => {
4443
+ if (!change.patch) return [];
4444
+ if (hasPatchFileHeader(change.patch)) return [change.patch];
4445
+ return [`*** ${patchHeaderChangeKind(change.kind)} File: ${change.path}\n${change.patch}`];
4446
+ }).join("\n");
4447
+ }
4448
+ function hasPatchFileHeader(patch) {
4449
+ return /^(?:diff --git |\*\*\* (?:Add|Update|Delete) File: )/m.test(patch);
4450
+ }
4451
+ function patchHeaderChangeKind(kind) {
4452
+ if (kind === "added") return "Add";
4453
+ if (kind === "deleted") return "Delete";
4454
+ return "Update";
4455
+ }
4284
4456
  function rolloutCustomToolOutputText(payload) {
4285
4457
  const rawOutput = firstString(payload, ["output"]);
4286
4458
  if (!rawOutput) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-relay",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "@noble/ciphers": "2.2.0",
41
41
  "@noble/curves": "2.2.0",
42
42
  "@noble/hashes": "2.2.0",
43
- "@openai/codex-sdk": "^0.130.0",
43
+ "@openai/codex-sdk": "0.137.0",
44
44
  "@tursodatabase/database": "^0.5.3",
45
45
  "base64-js": "1.5.1",
46
46
  "commander": "^14.0.3",
package/src/api-schema.ts CHANGED
@@ -32,6 +32,14 @@ export const RuntimeModeSchema = z.enum(["default", "auto", "full-access", "on-r
32
32
  export const SandboxModeSchema = z.enum(["workspace-write", "danger-full-access", "read-only"]);
33
33
  export const ReasoningEffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh"]);
34
34
  export const ThreadCollaborationModeSchema = z.enum(["default", "plan"]);
35
+ export const ThreadGoalStatusSchema = z.enum([
36
+ "active",
37
+ "paused",
38
+ "blocked",
39
+ "usageLimited",
40
+ "budgetLimited",
41
+ "complete",
42
+ ]);
35
43
 
36
44
  export const VersionResponseSchema = z.object({
37
45
  ok: z.boolean(),
@@ -139,6 +147,17 @@ export const ThreadContextWindowResponseSchema = z.object({
139
147
  rolloutPath: z.string().nullable().optional(),
140
148
  });
141
149
 
150
+ export const ThreadGoalSchema = z.object({
151
+ threadId: z.string().min(1),
152
+ objective: z.string().trim().min(1),
153
+ status: ThreadGoalStatusSchema,
154
+ tokenBudget: z.number().int().positive().nullable(),
155
+ tokensUsed: z.number().int().nonnegative(),
156
+ timeUsedSeconds: z.number().int().nonnegative(),
157
+ createdAt: IsoDateTimeSchema,
158
+ updatedAt: IsoDateTimeSchema,
159
+ });
160
+
142
161
  const PromptAttachmentBaseSchema = z.object({
143
162
  type: z.literal("image"),
144
163
  mimeType: z.string().trim().startsWith("image/").optional(),
@@ -266,6 +285,7 @@ export const ThreadSummarySchema = z.object({
266
285
  lastPrompt: z.string().optional(),
267
286
  lastResult: z.string().optional(),
268
287
  lastError: z.string().optional(),
288
+ goal: ThreadGoalSchema.nullable().optional(),
269
289
  });
270
290
 
271
291
  export const StatusResponseSchema = z.object({
@@ -567,6 +587,21 @@ export const QueuedThreadInputActionResponseSchema = z.object({
567
587
  thread: ThreadSummarySchema,
568
588
  });
569
589
 
590
+ export const UpdateThreadGoalRequestSchema = z
591
+ .object({
592
+ objective: z.string().trim().min(1).optional(),
593
+ status: ThreadGoalStatusSchema.optional(),
594
+ tokenBudget: z.number().int().positive().nullable().optional(),
595
+ })
596
+ .refine((input) => input.objective || input.status || input.tokenBudget !== undefined, {
597
+ message: "At least one goal field is required.",
598
+ });
599
+
600
+ export const ThreadGoalResponseSchema = z.object({
601
+ goal: ThreadGoalSchema.nullable(),
602
+ thread: ThreadSummarySchema,
603
+ });
604
+
570
605
  export const InterruptThreadRunResponseSchema = z.object({
571
606
  thread: ThreadSummarySchema,
572
607
  });
@@ -642,6 +677,11 @@ export const StreamThreadRunEventSchema = z.discriminatedUnion("type", [
642
677
  type: z.literal("thread.state.changed"),
643
678
  thread: ThreadSummarySchema,
644
679
  }),
680
+ z.object({
681
+ type: z.literal("thread.goal.updated"),
682
+ thread: ThreadSummarySchema,
683
+ goal: ThreadGoalSchema.nullable(),
684
+ }),
645
685
  z.object({
646
686
  type: z.literal("thread.error"),
647
687
  thread: ThreadSummarySchema.optional(),
@@ -691,6 +731,9 @@ export type RateLimitBucket = z.infer<typeof RateLimitBucketSchema>;
691
731
  export type RateLimitWindow = z.infer<typeof RateLimitWindowSchema>;
692
732
  export type RateLimitsResponse = z.infer<typeof RateLimitsResponseSchema>;
693
733
  export type ThreadContextWindowResponse = z.infer<typeof ThreadContextWindowResponseSchema>;
734
+ export type ThreadGoal = z.infer<typeof ThreadGoalSchema>;
735
+ export type ThreadGoalResponse = z.infer<typeof ThreadGoalResponseSchema>;
736
+ export type ThreadGoalStatus = z.infer<typeof ThreadGoalStatusSchema>;
694
737
  export type PromptAttachment = z.infer<typeof PromptAttachmentSchema>;
695
738
  export type PendingInputRequest = z.infer<typeof PendingInputRequestSchema>;
696
739
  export type PendingInputRequestQuestion = z.infer<typeof PendingInputRequestQuestionSchema>;
@@ -734,6 +777,7 @@ export type ImageAttachmentUploadResponse = z.infer<typeof ImageAttachmentUpload
734
777
  export type QueuedThreadInput = z.infer<typeof QueuedThreadInputSchema>;
735
778
  export type SubmitThreadInputResponse = z.infer<typeof SubmitThreadInputResponseSchema>;
736
779
  export type QueuedThreadInputActionResponse = z.infer<typeof QueuedThreadInputActionResponseSchema>;
780
+ export type UpdateThreadGoalRequest = z.infer<typeof UpdateThreadGoalRequestSchema>;
737
781
  export type InterruptThreadRunResponse = z.infer<typeof InterruptThreadRunResponseSchema>;
738
782
  export type ListQueuedThreadInputsResponse = z.infer<typeof ListQueuedThreadInputsResponseSchema>;
739
783
  export type ApprovalDecision = z.infer<typeof ApprovalDecisionSchema>;
@@ -988,6 +1032,7 @@ export const apiPaths = {
988
1032
  threadArchive: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}`,
989
1033
  threadContextWindow: (threadId: string) =>
990
1034
  `/v1/threads/${encodeURIComponent(threadId)}/context-window`,
1035
+ threadGoal: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}/goal`,
991
1036
  threadMessageDetail: (threadId: string, messageId: string, field: ThreadMessageDetailField) =>
992
1037
  `/v1/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/details/${encodeURIComponent(field)}`,
993
1038
  approval: (approvalId: string) => `/v1/approvals/${encodeURIComponent(approvalId)}`,
@@ -1007,7 +1052,7 @@ export function createOpenApiDocument() {
1007
1052
  openapi: "3.1.0",
1008
1053
  info: {
1009
1054
  title: "Codex Relay Local Codex API",
1010
- version: "0.1.0",
1055
+ version: "1.2.0",
1011
1056
  },
1012
1057
  paths: {
1013
1058
  "/version": {
@@ -1162,6 +1207,36 @@ export function createOpenApiDocument() {
1162
1207
  },
1163
1208
  },
1164
1209
  },
1210
+ "/v1/threads/{threadId}/goal": {
1211
+ get: {
1212
+ summary: "Read the active goal for a Codex app-server thread",
1213
+ responses: {
1214
+ "200": jsonResponse("ThreadGoalResponse"),
1215
+ "404": jsonResponse("ErrorResponse"),
1216
+ "502": jsonResponse("ErrorResponse"),
1217
+ },
1218
+ },
1219
+ post: {
1220
+ summary: "Update the active goal for a Codex app-server thread",
1221
+ requestBody: jsonRequest("UpdateThreadGoalRequest"),
1222
+ responses: {
1223
+ "200": jsonResponse("ThreadGoalResponse"),
1224
+ "400": jsonResponse("ErrorResponse"),
1225
+ "404": jsonResponse("ErrorResponse"),
1226
+ "409": jsonResponse("ErrorResponse"),
1227
+ "502": jsonResponse("ErrorResponse"),
1228
+ },
1229
+ },
1230
+ delete: {
1231
+ summary: "Clear the active goal for a Codex app-server thread",
1232
+ responses: {
1233
+ "200": jsonResponse("ThreadGoalResponse"),
1234
+ "404": jsonResponse("ErrorResponse"),
1235
+ "409": jsonResponse("ErrorResponse"),
1236
+ "502": jsonResponse("ErrorResponse"),
1237
+ },
1238
+ },
1239
+ },
1165
1240
  "/v1/threads/{threadId}/input": {
1166
1241
  post: {
1167
1242
  summary: "Submit input to an already-running Codex thread",
@@ -1245,6 +1320,50 @@ export function createOpenApiDocument() {
1245
1320
  lastPrompt: { type: "string" },
1246
1321
  lastResult: { type: "string" },
1247
1322
  lastError: { type: "string" },
1323
+ goal: {
1324
+ anyOf: [{ $ref: "#/components/schemas/ThreadGoal" }, { type: "null" }],
1325
+ },
1326
+ },
1327
+ },
1328
+ ThreadGoal: {
1329
+ type: "object",
1330
+ required: [
1331
+ "threadId",
1332
+ "objective",
1333
+ "status",
1334
+ "tokenBudget",
1335
+ "tokensUsed",
1336
+ "timeUsedSeconds",
1337
+ "createdAt",
1338
+ "updatedAt",
1339
+ ],
1340
+ properties: {
1341
+ threadId: { type: "string" },
1342
+ objective: { type: "string" },
1343
+ status: { type: "string", enum: ThreadGoalStatusSchema.options },
1344
+ tokenBudget: { anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }] },
1345
+ tokensUsed: { type: "integer", minimum: 0 },
1346
+ timeUsedSeconds: { type: "integer", minimum: 0 },
1347
+ createdAt: { type: "string", format: "date-time" },
1348
+ updatedAt: { type: "string", format: "date-time" },
1349
+ },
1350
+ },
1351
+ UpdateThreadGoalRequest: {
1352
+ type: "object",
1353
+ properties: {
1354
+ objective: { type: "string" },
1355
+ status: { type: "string", enum: ThreadGoalStatusSchema.options },
1356
+ tokenBudget: { anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }] },
1357
+ },
1358
+ },
1359
+ ThreadGoalResponse: {
1360
+ type: "object",
1361
+ required: ["goal", "thread"],
1362
+ properties: {
1363
+ goal: {
1364
+ anyOf: [{ $ref: "#/components/schemas/ThreadGoal" }, { type: "null" }],
1365
+ },
1366
+ thread: { $ref: "#/components/schemas/ThreadSummary" },
1248
1367
  },
1249
1368
  },
1250
1369
  StatusResponse: {