Skip to content

Commit b8c0c09

Browse files
ross-rlclaude
andauthored
feat(sdk): codex thread-goal wrappers (thread/goal/set|get|clear) (#147)
## Summary - Follow-up to #146: adds `setThreadGoal()`, `getThreadGoal()`, and `clearThreadGoal()` to `CodexAxonConnection`, wrapping `thread/goal/set`, `thread/goal/get`, and `thread/goal/clear`. - Same explicit-wrapper shape as the #146 methods; thread-scoped with the standard `not_connected` guard; types were already vendored in the generated v2 bindings. - Backs a downstream `/goal` slash command. ## Test plan - Wire-level assertions for all three methods (threadId injection, params passthrough) and `not_connected` rejection tests. Full suite: 579 tests pass; typecheck and biome clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c4ed6dd commit b8c0c09

3 files changed

Lines changed: 57 additions & 0 deletions

File tree

sdk/src/codex/connection.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ describe("CodexAxonConnection", () => {
198198
await conn.setThreadName("renamed");
199199
await conn.listMcpServerStatus();
200200
await conn.listSkills({ forceReload: true });
201+
await conn.setThreadGoal({ objective: "ship it" });
202+
await conn.getThreadGoal();
203+
await conn.clearThreadGoal();
201204
const frames = mock.axon.publish.mock.calls.map(([event]) => JSON.parse(event.payload));
202205
const byMethod = Object.fromEntries(frames.map((frame) => [frame.method, frame]));
203206
// Option<()> wire params: the field must be absent, not an empty object.
@@ -211,13 +214,23 @@ describe("CodexAxonConnection", () => {
211214
});
212215
expect(byMethod["mcpServerStatus/list"]).toMatchObject({ params: {} });
213216
expect(byMethod["skills/list"]).toMatchObject({ params: { forceReload: true } });
217+
expect(byMethod["thread/goal/set"]).toMatchObject({
218+
params: { threadId: "thr-1", objective: "ship it" },
219+
});
220+
expect(byMethod["thread/goal/get"]).toMatchObject({ params: { threadId: "thr-1" } });
221+
expect(byMethod["thread/goal/clear"]).toMatchObject({ params: { threadId: "thr-1" } });
214222
});
215223

216224
it("rejects thread-scoped wrappers before a thread exists", async () => {
217225
const { conn } = setup();
218226
await conn.connect();
219227
await expect(conn.readThread()).rejects.toMatchObject({ code: "not_connected" });
220228
await expect(conn.setThreadName("nope")).rejects.toMatchObject({ code: "not_connected" });
229+
await expect(conn.setThreadGoal({ objective: "x" })).rejects.toMatchObject({
230+
code: "not_connected",
231+
});
232+
await expect(conn.getThreadGoal()).rejects.toMatchObject({ code: "not_connected" });
233+
await expect(conn.clearThreadGoal()).rejects.toMatchObject({ code: "not_connected" });
221234
});
222235

223236
it("correlates responses by JSON-RPC id", async () => {

sdk/src/codex/connection.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ import type {
3434
ServerRequest,
3535
SkillsListParams,
3636
SkillsListResponse,
37+
ThreadGoalClearResponse,
38+
ThreadGoalGetResponse,
39+
ThreadGoalSetParams,
40+
ThreadGoalSetResponse,
3741
ThreadReadParams,
3842
ThreadReadResponse,
3943
ThreadSetNameResponse,
@@ -605,6 +609,42 @@ export class CodexAxonConnection {
605609
name,
606610
})) as ThreadSetNameResponse;
607611
}
612+
/** Sets or updates the active thread's goal (`thread/goal/set`). */
613+
async setThreadGoal(
614+
params: Omit<ThreadGoalSetParams, "threadId">,
615+
): Promise<ThreadGoalSetResponse> {
616+
if (!this._threadId)
617+
throw new ConnectionStateError(
618+
"not_connected",
619+
"No active thread. Call startThread() first.",
620+
);
621+
return (await this.request("thread/goal/set", {
622+
...params,
623+
threadId: this._threadId,
624+
})) as ThreadGoalSetResponse;
625+
}
626+
/** Reads the active thread's goal (`thread/goal/get`). */
627+
async getThreadGoal(): Promise<ThreadGoalGetResponse> {
628+
if (!this._threadId)
629+
throw new ConnectionStateError(
630+
"not_connected",
631+
"No active thread. Call startThread() first.",
632+
);
633+
return (await this.request("thread/goal/get", {
634+
threadId: this._threadId,
635+
})) as ThreadGoalGetResponse;
636+
}
637+
/** Clears the active thread's goal (`thread/goal/clear`). */
638+
async clearThreadGoal(): Promise<ThreadGoalClearResponse> {
639+
if (!this._threadId)
640+
throw new ConnectionStateError(
641+
"not_connected",
642+
"No active thread. Call startThread() first.",
643+
);
644+
return (await this.request("thread/goal/clear", {
645+
threadId: this._threadId,
646+
})) as ThreadGoalClearResponse;
647+
}
608648
/** Lists configured MCP servers with startup/auth status (`mcpServerStatus/list`). */
609649
async listMcpServerStatus(
610650
params: ListMcpServerStatusParams = {},

sdk/src/codex/protocol/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export type {
1212
ReviewTarget,
1313
SkillsListParams,
1414
SkillsListResponse,
15+
ThreadGoalClearResponse,
16+
ThreadGoalGetResponse,
17+
ThreadGoalSetParams,
18+
ThreadGoalSetResponse,
1519
ThreadReadParams,
1620
ThreadReadResponse,
1721
ThreadSetNameParams,

0 commit comments

Comments
 (0)