@agentex/agent 0.0.27 → 0.0.28
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/CHANGELOG.md +108 -0
- package/README.md +58 -7
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +99 -2
- package/dist/derived.js.map +1 -1
- package/dist/goals/controller.d.ts.map +1 -1
- package/dist/goals/controller.js +21 -0
- package/dist/goals/controller.js.map +1 -1
- package/dist/goals/normalize.d.ts +1 -0
- package/dist/goals/normalize.d.ts.map +1 -1
- package/dist/goals/normalize.js +5 -0
- package/dist/goals/normalize.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/attach.d.ts.map +1 -1
- package/dist/providers/claude/attach.js +50 -9
- package/dist/providers/claude/attach.js.map +1 -1
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +8 -0
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +2 -0
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +17 -10
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts.map +1 -1
- package/dist/providers/codex/attach.js +40 -3
- package/dist/providers/codex/attach.js.map +1 -1
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +8 -0
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/transcript.d.ts +3 -1
- package/dist/providers/codex/transcript.d.ts.map +1 -1
- package/dist/providers/codex/transcript.js +3 -1
- package/dist/providers/codex/transcript.js.map +1 -1
- package/dist/providers/cursor/discovery.d.ts +6 -0
- package/dist/providers/cursor/discovery.d.ts.map +1 -0
- package/dist/providers/cursor/discovery.js +118 -0
- package/dist/providers/cursor/discovery.js.map +1 -0
- package/dist/providers/cursor/execute.d.ts.map +1 -1
- package/dist/providers/cursor/execute.js +83 -45
- package/dist/providers/cursor/execute.js.map +1 -1
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +17 -4
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/cursor/parse.js +1 -1
- package/dist/providers/cursor/parse.js.map +1 -1
- package/dist/providers/cursor/probe.d.ts +3 -0
- package/dist/providers/cursor/probe.d.ts.map +1 -0
- package/dist/providers/cursor/probe.js +89 -0
- package/dist/providers/cursor/probe.js.map +1 -0
- package/dist/providers/cursor/runtime.d.ts +4 -0
- package/dist/providers/cursor/runtime.d.ts.map +1 -0
- package/dist/providers/cursor/runtime.js +12 -0
- package/dist/providers/cursor/runtime.js.map +1 -0
- package/dist/providers/cursor/session.d.ts +3 -0
- package/dist/providers/cursor/session.d.ts.map +1 -0
- package/dist/providers/cursor/session.js +14 -0
- package/dist/providers/cursor/session.js.map +1 -0
- package/dist/providers/opencode/client.d.ts +14 -0
- package/dist/providers/opencode/client.d.ts.map +1 -0
- package/dist/providers/opencode/client.js +40 -0
- package/dist/providers/opencode/client.js.map +1 -0
- package/dist/providers/opencode/discovery.d.ts +4 -0
- package/dist/providers/opencode/discovery.d.ts.map +1 -0
- package/dist/providers/opencode/discovery.js +68 -0
- package/dist/providers/opencode/discovery.js.map +1 -0
- package/dist/providers/opencode/execute.d.ts.map +1 -1
- package/dist/providers/opencode/execute.js +219 -164
- package/dist/providers/opencode/execute.js.map +1 -1
- package/dist/providers/opencode/history.d.ts +24 -0
- package/dist/providers/opencode/history.d.ts.map +1 -0
- package/dist/providers/opencode/history.js +242 -0
- package/dist/providers/opencode/history.js.map +1 -0
- package/dist/providers/opencode/http-session.d.ts.map +1 -1
- package/dist/providers/opencode/http-session.js +333 -49
- package/dist/providers/opencode/http-session.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +26 -3
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/opencode/manager.d.ts +11 -0
- package/dist/providers/opencode/manager.d.ts.map +1 -0
- package/dist/providers/opencode/manager.js +240 -0
- package/dist/providers/opencode/manager.js.map +1 -0
- package/dist/providers/opencode/modes.d.ts +3 -0
- package/dist/providers/opencode/modes.d.ts.map +1 -0
- package/dist/providers/opencode/modes.js +30 -0
- package/dist/providers/opencode/modes.js.map +1 -0
- package/dist/providers/opencode/parse.d.ts.map +1 -1
- package/dist/providers/opencode/parse.js +2 -22
- package/dist/providers/opencode/parse.js.map +1 -1
- package/dist/providers/opencode/probe.d.ts +3 -0
- package/dist/providers/opencode/probe.d.ts.map +1 -0
- package/dist/providers/opencode/probe.js +107 -0
- package/dist/providers/opencode/probe.js.map +1 -0
- package/dist/providers/opencode/runtime.d.ts +8 -0
- package/dist/providers/opencode/runtime.d.ts.map +1 -0
- package/dist/providers/opencode/runtime.js +12 -0
- package/dist/providers/opencode/runtime.js.map +1 -0
- package/dist/providers/opencode/server.d.ts +18 -3
- package/dist/providers/opencode/server.d.ts.map +1 -1
- package/dist/providers/opencode/server.js +0 -0
- package/dist/providers/opencode/server.js.map +1 -1
- package/dist/providers/opencode/skill-config.d.ts +11 -0
- package/dist/providers/opencode/skill-config.d.ts.map +1 -0
- package/dist/providers/opencode/skill-config.js +57 -0
- package/dist/providers/opencode/skill-config.js.map +1 -0
- package/dist/sessions/exec-backed.d.ts +10 -0
- package/dist/sessions/exec-backed.d.ts.map +1 -0
- package/dist/sessions/exec-backed.js +141 -0
- package/dist/sessions/exec-backed.js.map +1 -0
- package/dist/sessions/history.d.ts +4 -0
- package/dist/sessions/history.d.ts.map +1 -0
- package/dist/sessions/history.js +59 -0
- package/dist/sessions/history.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/types.d.ts +147 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +36 -6
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/binary.d.ts.map +1 -1
- package/dist/utils/binary.js +3 -0
- package/dist/utils/binary.js.map +1 -1
- package/dist/utils/endpoint.js +3 -3
- package/dist/utils/endpoint.js.map +1 -1
- package/package.json +4 -2
- package/src/derived.ts +126 -3
- package/src/goals/controller.ts +17 -0
- package/src/goals/normalize.ts +5 -0
- package/src/index.ts +13 -0
- package/src/providers/claude/attach.ts +48 -6
- package/src/providers/claude/index.ts +8 -0
- package/src/providers/claude/session.ts +15 -10
- package/src/providers/codex/attach.ts +48 -3
- package/src/providers/codex/index.ts +8 -0
- package/src/providers/codex/transcript.ts +5 -1
- package/src/providers/cursor/discovery.ts +114 -0
- package/src/providers/cursor/execute.ts +67 -30
- package/src/providers/cursor/index.ts +19 -5
- package/src/providers/cursor/parse.ts +1 -1
- package/src/providers/cursor/probe.ts +90 -0
- package/src/providers/cursor/runtime.ts +11 -0
- package/src/providers/cursor/session.ts +15 -0
- package/src/providers/opencode/client.ts +44 -0
- package/src/providers/opencode/discovery.ts +69 -0
- package/src/providers/opencode/execute.ts +64 -12
- package/src/providers/opencode/history.ts +270 -0
- package/src/providers/opencode/http-session.ts +319 -50
- package/src/providers/opencode/index.ts +26 -3
- package/src/providers/opencode/manager.ts +269 -0
- package/src/providers/opencode/modes.ts +31 -0
- package/src/providers/opencode/parse.ts +2 -22
- package/src/providers/opencode/probe.ts +113 -0
- package/src/providers/opencode/runtime.ts +13 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/opencode/skill-config.ts +64 -0
- package/src/sessions/exec-backed.ts +169 -0
- package/src/sessions/history.ts +76 -0
- package/src/sessions/index.ts +3 -0
- package/src/types.ts +158 -3
- package/src/utils/auth.ts +35 -6
- package/src/utils/binary.ts +3 -0
- package/src/utils/endpoint.ts +3 -3
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentSession,
|
|
3
|
+
CancelResult,
|
|
4
|
+
ClearGoalResult,
|
|
5
|
+
ExecutionContext,
|
|
6
|
+
ExecutionResult,
|
|
7
|
+
GoalOptions,
|
|
8
|
+
GoalState,
|
|
9
|
+
ProviderCapabilities,
|
|
10
|
+
SendHandle,
|
|
11
|
+
SendOptions,
|
|
12
|
+
SessionCodec,
|
|
13
|
+
SessionContext,
|
|
14
|
+
SessionState,
|
|
15
|
+
SetGoalResult,
|
|
16
|
+
StopTaskResult,
|
|
17
|
+
StreamEvent,
|
|
18
|
+
TurnResult,
|
|
19
|
+
} from "../types.js";
|
|
20
|
+
import { GoalController, EMULATED_GOAL_CAPABILITY } from "../goals/index.js";
|
|
21
|
+
import { uuidv7 } from "../utils/uuid.js";
|
|
22
|
+
|
|
23
|
+
export interface ExecBackedSessionOptions {
|
|
24
|
+
providerType: string;
|
|
25
|
+
execute(ctx: ExecutionContext): Promise<ExecutionResult>;
|
|
26
|
+
sessionCodec: SessionCodec;
|
|
27
|
+
ctx: SessionContext;
|
|
28
|
+
capabilities?: Pick<ProviderCapabilities, "goals">;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function turnResult(result: ExecutionResult): TurnResult {
|
|
32
|
+
return {
|
|
33
|
+
summary: result.summary,
|
|
34
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
35
|
+
costUsd: result.costUsd,
|
|
36
|
+
status: result.status === "blocked" ? "failed" : result.status,
|
|
37
|
+
errorCode: result.errorCode,
|
|
38
|
+
errorMessage: result.errorMessage,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createExecBackedSession(options: ExecBackedSessionOptions): AgentSession {
|
|
43
|
+
return new ExecBackedSession(options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class ExecBackedSession implements AgentSession {
|
|
47
|
+
private _state: SessionState = "idle";
|
|
48
|
+
private params: Record<string, unknown> | null;
|
|
49
|
+
private active: AbortController | null = null;
|
|
50
|
+
private inFlight: Promise<TurnResult> | null = null;
|
|
51
|
+
private draining = false;
|
|
52
|
+
private readonly goals: GoalController;
|
|
53
|
+
|
|
54
|
+
constructor(private readonly options: ExecBackedSessionOptions) {
|
|
55
|
+
this.params = options.sessionCodec.deserialize(options.ctx.sessionParams ?? null);
|
|
56
|
+
this.goals = new GoalController({
|
|
57
|
+
providerType: options.providerType,
|
|
58
|
+
capability: options.capabilities?.goals ?? EMULATED_GOAL_CAPABILITY,
|
|
59
|
+
getSessionId: () => this.sessionId,
|
|
60
|
+
send: (message) => this.send(message),
|
|
61
|
+
dispatch: (event) => void Promise.resolve(options.ctx.onEvent?.(event)).catch(() => undefined),
|
|
62
|
+
});
|
|
63
|
+
if (options.ctx.signal) {
|
|
64
|
+
if (options.ctx.signal.aborted) void this.close();
|
|
65
|
+
else options.ctx.signal.addEventListener("abort", () => void this.close(), { once: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get sessionId(): string | null {
|
|
70
|
+
return this.options.sessionCodec.getDisplayId?.(this.params) ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get state(): SessionState {
|
|
74
|
+
return this._state;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async send(message: string, sendOptions?: SendOptions): Promise<SendHandle> {
|
|
78
|
+
if (this._state === "closed") throw new Error("Session is closed");
|
|
79
|
+
if (this.draining) throw new Error("Session is draining");
|
|
80
|
+
if (this.inFlight) throw new Error(`${this.options.providerType} session is busy`);
|
|
81
|
+
const uuid = uuidv7();
|
|
82
|
+
const result = this.run(message, sendOptions);
|
|
83
|
+
this.inFlight = result;
|
|
84
|
+
void result.then((settled) => this.goals.onTurnSettled(settled)).catch(() => undefined);
|
|
85
|
+
return { uuid, result };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async run(message: string, sendOptions?: SendOptions): Promise<TurnResult> {
|
|
89
|
+
this._state = "thinking";
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
this.active = controller;
|
|
92
|
+
const onAbort = () => controller.abort();
|
|
93
|
+
sendOptions?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
try {
|
|
95
|
+
const result = await this.options.execute({
|
|
96
|
+
prompt: message,
|
|
97
|
+
cwd: this.options.ctx.cwd,
|
|
98
|
+
env: this.options.ctx.env,
|
|
99
|
+
config: {
|
|
100
|
+
...this.options.ctx.config,
|
|
101
|
+
...(sendOptions?.timeoutSec !== undefined ? { timeoutSec: sendOptions.timeoutSec } : {}),
|
|
102
|
+
},
|
|
103
|
+
sessionParams: this.params,
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
onOutput: this.options.ctx.onOutput,
|
|
106
|
+
onLifecycle: (event) => this.options.ctx.onLifecycle?.(event),
|
|
107
|
+
onEvent: async (event: StreamEvent) => {
|
|
108
|
+
if (event.sessionId) {
|
|
109
|
+
this.params = this.options.sessionCodec.serialize({
|
|
110
|
+
sessionId: event.sessionId,
|
|
111
|
+
...(this.options.ctx.cwd ? { cwd: this.options.ctx.cwd } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (event.type === "tool_call") this._state = "tool_executing";
|
|
115
|
+
else if (event.type === "assistant" || event.type === "thinking") this._state = "thinking";
|
|
116
|
+
await this.options.ctx.onEvent?.(event);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
if (result.clearSession) this.params = null;
|
|
120
|
+
if (result.sessionParams) this.params = this.options.sessionCodec.serialize(result.sessionParams);
|
|
121
|
+
return turnResult(result);
|
|
122
|
+
} finally {
|
|
123
|
+
sendOptions?.signal?.removeEventListener("abort", onAbort);
|
|
124
|
+
this.active = null;
|
|
125
|
+
this.inFlight = null;
|
|
126
|
+
this.restoreIdle();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private restoreIdle(): void {
|
|
131
|
+
if (this._state !== "closed") this._state = "idle";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async cancel(_uuid: string): Promise<CancelResult> {
|
|
135
|
+
return { cancelled: false };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async stopTask(_taskId: string): Promise<StopTaskResult> {
|
|
139
|
+
return { stopped: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setGoal(objective: string, goalOptions?: GoalOptions): Promise<SetGoalResult> {
|
|
143
|
+
return this.goals.setGoal(objective, goalOptions);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
clearGoal(clearOptions?: { reason?: "cleared" | "blocked" }): Promise<ClearGoalResult> {
|
|
147
|
+
return this.goals.clearGoal(clearOptions);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getGoal(): GoalState | null {
|
|
151
|
+
return this.goals.getGoal();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async interrupt(): Promise<void> {
|
|
155
|
+
this.active?.abort();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async drain(): Promise<void> {
|
|
159
|
+
this.draining = true;
|
|
160
|
+
if (this.inFlight) await this.inFlight.catch(() => undefined);
|
|
161
|
+
await this.close();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async close(): Promise<void> {
|
|
165
|
+
this._state = "closed";
|
|
166
|
+
this.active?.abort();
|
|
167
|
+
if (this.inFlight) await this.inFlight.catch(() => undefined);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HistoryAttachment,
|
|
3
|
+
HistoryCatchUpOptions,
|
|
4
|
+
HistoryCheckpoint,
|
|
5
|
+
SessionAttachment,
|
|
6
|
+
} from "../types.js";
|
|
7
|
+
|
|
8
|
+
interface FileCheckpointValue {
|
|
9
|
+
offset: number;
|
|
10
|
+
eventId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function decodeCheckpoint(
|
|
14
|
+
checkpoint: HistoryCheckpoint | undefined,
|
|
15
|
+
kind: string,
|
|
16
|
+
): FileCheckpointValue | undefined {
|
|
17
|
+
if (!checkpoint) return undefined;
|
|
18
|
+
if (checkpoint.kind !== kind) {
|
|
19
|
+
throw new Error(`history checkpoint kind must be ${kind}`);
|
|
20
|
+
}
|
|
21
|
+
const value = checkpoint.value;
|
|
22
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23
|
+
throw new Error("history checkpoint value must be an object");
|
|
24
|
+
}
|
|
25
|
+
const offset = (value as Record<string, unknown>)["offset"];
|
|
26
|
+
const eventId = (value as Record<string, unknown>)["eventId"];
|
|
27
|
+
if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0) {
|
|
28
|
+
throw new Error("history checkpoint offset must be a non-negative safe integer");
|
|
29
|
+
}
|
|
30
|
+
if (eventId !== undefined && typeof eventId !== "string") {
|
|
31
|
+
throw new Error("history checkpoint eventId must be a string");
|
|
32
|
+
}
|
|
33
|
+
return { offset, ...(eventId ? { eventId } : {}) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Adapt the legacy byte-offset transcript contract to the additive history API. */
|
|
37
|
+
export function historyFromSessionAttachment(
|
|
38
|
+
providerType: string,
|
|
39
|
+
attachment: SessionAttachment,
|
|
40
|
+
): HistoryAttachment {
|
|
41
|
+
const checkpointKind = `${providerType}:byte-offset:v1`;
|
|
42
|
+
return {
|
|
43
|
+
record: attachment.record,
|
|
44
|
+
historySource: attachment.transcript
|
|
45
|
+
? { kind: "file", path: attachment.transcript.filePath }
|
|
46
|
+
: null,
|
|
47
|
+
lastTurn: attachment.lastTurn,
|
|
48
|
+
catchUp(options?: HistoryCatchUpOptions) {
|
|
49
|
+
if (options?.mode === "bounded_full_resync") {
|
|
50
|
+
options = { ...options, after: undefined };
|
|
51
|
+
}
|
|
52
|
+
const decoded = decodeCheckpoint(options?.after, checkpointKind);
|
|
53
|
+
return {
|
|
54
|
+
async *[Symbol.asyncIterator]() {
|
|
55
|
+
for await (const yielded of attachment.catchUp({
|
|
56
|
+
...(decoded ? { fromOffset: decoded.offset } : {}),
|
|
57
|
+
...(decoded?.eventId ? { sinceEventId: decoded.eventId } : {}),
|
|
58
|
+
})) {
|
|
59
|
+
yield {
|
|
60
|
+
event: yielded.event,
|
|
61
|
+
eventId: yielded.eventId,
|
|
62
|
+
checkpoint: {
|
|
63
|
+
kind: checkpointKind,
|
|
64
|
+
value: {
|
|
65
|
+
offset: yielded.offset,
|
|
66
|
+
...(yielded.eventId ? { eventId: yielded.eventId } : {}),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
resume: (ctx) => attachment.resume(ctx),
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/sessions/index.ts
CHANGED
|
@@ -6,3 +6,6 @@ export {
|
|
|
6
6
|
assertSessionRecord,
|
|
7
7
|
} from "./record.js";
|
|
8
8
|
export type { CreateSessionRecordInput } from "./record.js";
|
|
9
|
+
export { historyFromSessionAttachment } from "./history.js";
|
|
10
|
+
export { createExecBackedSession } from "./exec-backed.js";
|
|
11
|
+
export type { ExecBackedSessionOptions } from "./exec-backed.js";
|
package/src/types.ts
CHANGED
|
@@ -93,6 +93,49 @@ export interface ProviderCapabilities {
|
|
|
93
93
|
* Codex); absent elsewhere. See internal-docs/spec-durable-sessions.md.
|
|
94
94
|
*/
|
|
95
95
|
durableSessions?: boolean;
|
|
96
|
+
/** Provider-neutral durable history through `attachHistory`. */
|
|
97
|
+
durableHistory?: boolean;
|
|
98
|
+
/** A persisted provider session can be resumed. */
|
|
99
|
+
resume?: boolean;
|
|
100
|
+
/** Models expose provider-native variants independently from effort. */
|
|
101
|
+
modelVariants?: boolean;
|
|
102
|
+
/** The provider can surface interactive permission requests. */
|
|
103
|
+
permissionRequests?: boolean;
|
|
104
|
+
/** The provider can surface structured questions. */
|
|
105
|
+
questionRequests?: boolean;
|
|
106
|
+
/** Ambient MCP configuration can be excluded by a proved mechanism. */
|
|
107
|
+
strictMcpIsolation?: boolean;
|
|
108
|
+
/** Connected upstream-provider credentials can be removed safely. */
|
|
109
|
+
upstreamProviderDisconnect?: boolean;
|
|
110
|
+
/** Selection changes can be applied without starting a new host chat. */
|
|
111
|
+
sessionModelChange?: boolean;
|
|
112
|
+
sessionVariantChange?: boolean;
|
|
113
|
+
sessionEffortChange?: boolean;
|
|
114
|
+
sessionModeChange?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type CapabilityStatus = "supported" | "missing" | "upgrade_required" | "degraded";
|
|
118
|
+
|
|
119
|
+
export interface ProviderRuntimeContext {
|
|
120
|
+
cwd?: string;
|
|
121
|
+
env?: Record<string, string>;
|
|
122
|
+
config?: ProviderConfig;
|
|
123
|
+
refresh?: boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ProviderRuntimeReport {
|
|
127
|
+
binary: {
|
|
128
|
+
status: CapabilityStatus;
|
|
129
|
+
command: string | null;
|
|
130
|
+
version: string | null;
|
|
131
|
+
protocolProfile: string | null;
|
|
132
|
+
reason?: string;
|
|
133
|
+
};
|
|
134
|
+
capabilities: Partial<Record<keyof ProviderCapabilities, {
|
|
135
|
+
supported: boolean;
|
|
136
|
+
status: CapabilityStatus;
|
|
137
|
+
reason?: string;
|
|
138
|
+
}>>;
|
|
96
139
|
}
|
|
97
140
|
|
|
98
141
|
// ---------------------------------------------------------------------------
|
|
@@ -121,7 +164,7 @@ export interface ProviderCapabilities {
|
|
|
121
164
|
export type GoalStatus = "active" | "paused" | "met" | "blocked" | "cleared";
|
|
122
165
|
|
|
123
166
|
/** Why a `blocked` goal is blocked. */
|
|
124
|
-
export type GoalBlockedReason = "budget" | "needs_input" | "max_iterations";
|
|
167
|
+
export type GoalBlockedReason = "budget" | "needs_input" | "max_iterations" | "sentinel_error";
|
|
125
168
|
|
|
126
169
|
/** Who last changed the goal state. */
|
|
127
170
|
export type GoalSource = "host" | "model" | "sentinel" | "agentex";
|
|
@@ -165,6 +208,8 @@ export interface GoalState {
|
|
|
165
208
|
source: GoalSource;
|
|
166
209
|
/** Why a `blocked` goal is blocked. Absent unless status === "blocked". */
|
|
167
210
|
blockedReason?: GoalBlockedReason;
|
|
211
|
+
/** Error from goal evaluation when blockedReason is `sentinel_error`. */
|
|
212
|
+
errorMessage?: string;
|
|
168
213
|
/** Codex telemetry, when the provider reports it (reverse-engineered fields). */
|
|
169
214
|
tokensUsed?: number;
|
|
170
215
|
timeUsedSeconds?: number;
|
|
@@ -286,8 +331,12 @@ export interface ProviderModule {
|
|
|
286
331
|
*/
|
|
287
332
|
resolveAuth(ctx?: AuthResolveContext): Promise<AuthReport>;
|
|
288
333
|
sessionCodec?: SessionCodec;
|
|
289
|
-
/** List available models
|
|
290
|
-
listModels?(options?:
|
|
334
|
+
/** List available models in a runtime context. */
|
|
335
|
+
listModels?(options?: ListModelsOptions): Promise<ProviderModel[]>;
|
|
336
|
+
/** Probe the selected binary and wire protocol for effective capabilities. */
|
|
337
|
+
probeCapabilities?(ctx?: ProviderRuntimeContext): Promise<ProviderRuntimeReport>;
|
|
338
|
+
/** Optional management surface for harness-owned upstream providers. */
|
|
339
|
+
upstreamProviders?: UpstreamProviderManager;
|
|
291
340
|
/**
|
|
292
341
|
* List the operating modes this provider exposes (see `AgentMode`). Present
|
|
293
342
|
* only on providers with `capabilities.modes === true`. May spawn the agent
|
|
@@ -317,6 +366,8 @@ export interface ProviderModule {
|
|
|
317
366
|
* never auto-invoked). See internal-docs/spec-durable-sessions.md.
|
|
318
367
|
*/
|
|
319
368
|
attachSession?(record: SessionRecord, opts?: AttachOptions): Promise<SessionAttachment>;
|
|
369
|
+
/** Additive file-or-service durable history surface. */
|
|
370
|
+
attachHistory?(record: SessionRecord, opts?: AttachOptions): Promise<HistoryAttachment>;
|
|
320
371
|
}
|
|
321
372
|
|
|
322
373
|
// Result of looking up a transcript for a given session.
|
|
@@ -462,6 +513,8 @@ export interface CatchUpOptions {
|
|
|
462
513
|
export interface AttachOptions {
|
|
463
514
|
/** Env overlay for home-dir resolution (same vars the transcript helpers honor). */
|
|
464
515
|
env?: Record<string, string>;
|
|
516
|
+
/** Runtime selection for service-backed attachment. */
|
|
517
|
+
config?: ProviderConfig;
|
|
465
518
|
}
|
|
466
519
|
|
|
467
520
|
/** Read-only reattachment to a session's durable state. See `attachSession`. */
|
|
@@ -484,6 +537,36 @@ export interface SessionAttachment {
|
|
|
484
537
|
resume(ctx?: SessionContext): Promise<AgentSession>;
|
|
485
538
|
}
|
|
486
539
|
|
|
540
|
+
/** Opaque, provider-owned checkpoint for file or service history. */
|
|
541
|
+
export interface HistoryCheckpoint {
|
|
542
|
+
kind: string;
|
|
543
|
+
value: unknown;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export type HistorySource =
|
|
547
|
+
| { kind: "file"; path: string }
|
|
548
|
+
| { kind: "service"; description: string };
|
|
549
|
+
|
|
550
|
+
export interface HistoryCatchUpYield {
|
|
551
|
+
event: StreamEvent;
|
|
552
|
+
checkpoint: HistoryCheckpoint;
|
|
553
|
+
eventId: string | null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export interface HistoryCatchUpOptions {
|
|
557
|
+
after?: HistoryCheckpoint;
|
|
558
|
+
mode?: "incremental" | "bounded_full_resync";
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export interface HistoryAttachment {
|
|
562
|
+
record: SessionRecord;
|
|
563
|
+
historySource: HistorySource | null;
|
|
564
|
+
lastTurn: LastTurnStatus;
|
|
565
|
+
catchUp(opts?: HistoryCatchUpOptions): AsyncIterable<HistoryCatchUpYield>;
|
|
566
|
+
resume(ctx?: SessionContext): Promise<AgentSession>;
|
|
567
|
+
close?(): Promise<void>;
|
|
568
|
+
}
|
|
569
|
+
|
|
487
570
|
// Execution input
|
|
488
571
|
export interface ExecutionContext {
|
|
489
572
|
prompt: string;
|
|
@@ -567,9 +650,15 @@ export interface ProviderEndpointConfig {
|
|
|
567
650
|
export interface ProviderConfig {
|
|
568
651
|
command?: string;
|
|
569
652
|
model?: string;
|
|
653
|
+
/** Provider-native model variant, independent from reasoning effort. */
|
|
654
|
+
modelVariant?: string;
|
|
570
655
|
/** Provider reasoning effort. Claude maps this to `--effort`; Codex sends
|
|
571
656
|
* it as the app-server `turn/start.effort` override. */
|
|
572
657
|
effort?: string;
|
|
658
|
+
/** Behavior when a host does not provide or loses its input callback. */
|
|
659
|
+
unattendedPermissionPolicy?: "allow" | "deny";
|
|
660
|
+
/** Deadline for a host to answer an observed permission or question. Default 300s. */
|
|
661
|
+
inputRequestTimeoutSec?: number;
|
|
573
662
|
maxTurns?: number;
|
|
574
663
|
/**
|
|
575
664
|
* Hard runtime cap, in seconds.
|
|
@@ -1068,6 +1157,7 @@ export type StreamEvent =
|
|
|
1068
1157
|
enforced: boolean;
|
|
1069
1158
|
source: GoalSource;
|
|
1070
1159
|
blockedReason?: GoalBlockedReason;
|
|
1160
|
+
errorMessage?: string;
|
|
1071
1161
|
tokensUsed?: number;
|
|
1072
1162
|
timeUsedSeconds?: number;
|
|
1073
1163
|
tokenBudget?: number;
|
|
@@ -1229,6 +1319,69 @@ export interface ProviderModel {
|
|
|
1229
1319
|
id: string;
|
|
1230
1320
|
name: string;
|
|
1231
1321
|
provider?: string;
|
|
1322
|
+
providerName?: string;
|
|
1323
|
+
variants?: Array<{
|
|
1324
|
+
id: string;
|
|
1325
|
+
name: string;
|
|
1326
|
+
description?: string;
|
|
1327
|
+
isDefault?: boolean;
|
|
1328
|
+
disabled?: boolean;
|
|
1329
|
+
}>;
|
|
1330
|
+
supportedEfforts?: string[];
|
|
1331
|
+
defaultEffort?: string;
|
|
1332
|
+
contextWindow?: number;
|
|
1333
|
+
maxOutputTokens?: number;
|
|
1334
|
+
inputCostPerMillion?: number;
|
|
1335
|
+
outputCostPerMillion?: number;
|
|
1336
|
+
supportsImages?: boolean;
|
|
1337
|
+
supportsTools?: boolean;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
export interface ListModelsOptions extends ProviderRuntimeContext {
|
|
1341
|
+
cacheTtlMs?: number;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
export interface UpstreamProvider {
|
|
1345
|
+
id: string;
|
|
1346
|
+
name: string;
|
|
1347
|
+
connected: boolean;
|
|
1348
|
+
authMethodIds: string[];
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
export interface ProviderAuthMethod {
|
|
1352
|
+
id: string;
|
|
1353
|
+
name: string;
|
|
1354
|
+
type: "api_key" | "oauth";
|
|
1355
|
+
prompts?: Array<{
|
|
1356
|
+
id: string;
|
|
1357
|
+
label: string;
|
|
1358
|
+
type: "text" | "select";
|
|
1359
|
+
options?: Array<{ value: string; label: string }>;
|
|
1360
|
+
}>;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
export interface ProviderAuthFlow {
|
|
1364
|
+
id: string;
|
|
1365
|
+
providerId: string;
|
|
1366
|
+
url: string | null;
|
|
1367
|
+
completion: "code" | "automatic";
|
|
1368
|
+
instructions: string | null;
|
|
1369
|
+
expiresAt: string;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
export interface UpstreamProviderManager {
|
|
1373
|
+
list(ctx?: ProviderRuntimeContext): Promise<UpstreamProvider[]>;
|
|
1374
|
+
authMethods(providerId: string, ctx?: ProviderRuntimeContext): Promise<ProviderAuthMethod[]>;
|
|
1375
|
+
setApiKey(providerId: string, key: string, ctx?: ProviderRuntimeContext): Promise<void>;
|
|
1376
|
+
beginOAuth(
|
|
1377
|
+
providerId: string,
|
|
1378
|
+
methodId: string,
|
|
1379
|
+
inputs?: Record<string, string>,
|
|
1380
|
+
ctx?: ProviderRuntimeContext,
|
|
1381
|
+
): Promise<ProviderAuthFlow>;
|
|
1382
|
+
completeOAuth(flowId: string, code?: string, ctx?: ProviderRuntimeContext): Promise<void>;
|
|
1383
|
+
canDisconnect(providerId: string, ctx?: ProviderRuntimeContext): Promise<boolean>;
|
|
1384
|
+
disconnect(providerId: string, ctx?: ProviderRuntimeContext): Promise<void>;
|
|
1232
1385
|
}
|
|
1233
1386
|
|
|
1234
1387
|
// MCP server configuration
|
|
@@ -1496,6 +1649,8 @@ export interface AgentSession {
|
|
|
1496
1649
|
* `capabilities.durableSessions === true`.
|
|
1497
1650
|
*/
|
|
1498
1651
|
describe?(): SessionRecord | null;
|
|
1652
|
+
/** Additive provider-neutral durable-history identity. */
|
|
1653
|
+
describeHistory?(): SessionRecord | null;
|
|
1499
1654
|
}
|
|
1500
1655
|
|
|
1501
1656
|
/** Result of a single turn within a session. */
|
package/src/utils/auth.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function detectAuth(providerType: string, env: Record<string, string>): R
|
|
|
89
89
|
return { method: "subscription", billingType: "subscription" };
|
|
90
90
|
}
|
|
91
91
|
case "cursor": {
|
|
92
|
-
if (hasEnv(env, "CURSOR_API_KEY")
|
|
92
|
+
if (hasEnv(env, "CURSOR_API_KEY")) {
|
|
93
93
|
return { method: "api_key", billingType: "api" };
|
|
94
94
|
}
|
|
95
95
|
return { method: "subscription", billingType: "subscription" };
|
|
@@ -435,21 +435,50 @@ async function resolveGeminiAuth(ctx?: AuthResolveContext): Promise<AuthReport>
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
// ---------------------------------------------------------------------------
|
|
438
|
-
// Cursor
|
|
439
|
-
//
|
|
438
|
+
// Cursor uses its selected binary's `status` command. Other providers in this
|
|
439
|
+
// section use environment or native-store presence reports.
|
|
440
440
|
// ---------------------------------------------------------------------------
|
|
441
441
|
|
|
442
442
|
async function resolveCursorAuth(ctx?: AuthResolveContext): Promise<AuthReport> {
|
|
443
443
|
const env = buildEnv(ctx?.env);
|
|
444
444
|
const binary = await checkBinary("agent", ctx?.command, env);
|
|
445
|
+
const apiKey = {
|
|
446
|
+
method: "api_key" as const,
|
|
447
|
+
source: { kind: "env" as const, var: "CURSOR_API_KEY" },
|
|
448
|
+
present: hasEnv(env, "CURSOR_API_KEY"),
|
|
449
|
+
};
|
|
450
|
+
let nativePresent = false;
|
|
451
|
+
let usedCli = false;
|
|
452
|
+
if (binary.installed && binary.resolvedPath) {
|
|
453
|
+
try {
|
|
454
|
+
const result = await runChildProcess({
|
|
455
|
+
runId: "cursor-auth-status",
|
|
456
|
+
command: binary.resolvedPath,
|
|
457
|
+
args: [...binary.prefixArgs, "status"],
|
|
458
|
+
cwd: process.cwd(),
|
|
459
|
+
env,
|
|
460
|
+
timeoutSec: 10,
|
|
461
|
+
});
|
|
462
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
463
|
+
nativePresent = result.exitCode === 0 && /logged\s+in|authenticated|account\s*:/i.test(output)
|
|
464
|
+
&& !/not\s+logged\s+in|unauthenticated/i.test(output);
|
|
465
|
+
usedCli = true;
|
|
466
|
+
} catch {
|
|
467
|
+
// Presence remains unknown and false for an unsupported old binary.
|
|
468
|
+
}
|
|
469
|
+
}
|
|
445
470
|
return {
|
|
446
471
|
providerType: "cursor",
|
|
447
472
|
binary: stripPrefixArgs(binary),
|
|
448
473
|
options: [
|
|
449
|
-
|
|
450
|
-
{
|
|
474
|
+
apiKey,
|
|
475
|
+
{
|
|
476
|
+
method: "subscription",
|
|
477
|
+
source: { kind: "cli", command: "agent status" },
|
|
478
|
+
present: nativePresent,
|
|
479
|
+
},
|
|
451
480
|
],
|
|
452
|
-
source: "filesystem",
|
|
481
|
+
source: usedCli ? "cli" : "filesystem",
|
|
453
482
|
};
|
|
454
483
|
}
|
|
455
484
|
|
package/src/utils/binary.ts
CHANGED
|
@@ -28,8 +28,11 @@ const COMMON_PATHS_UNIX: Record<string, string[]> = {
|
|
|
28
28
|
],
|
|
29
29
|
agent: [
|
|
30
30
|
path.join(os.homedir(), ".local", "bin", "agent"),
|
|
31
|
+
path.join(os.homedir(), ".local", "bin", "cursor-agent"),
|
|
31
32
|
"/usr/local/bin/agent",
|
|
33
|
+
"/usr/local/bin/cursor-agent",
|
|
32
34
|
path.join(os.homedir(), ".npm-global", "bin", "agent"),
|
|
35
|
+
path.join(os.homedir(), ".npm-global", "bin", "cursor-agent"),
|
|
33
36
|
],
|
|
34
37
|
opencode: [
|
|
35
38
|
path.join(os.homedir(), ".local", "bin", "opencode"),
|
package/src/utils/endpoint.ts
CHANGED
|
@@ -158,13 +158,13 @@ function translateCodexEndpoint(e: ProviderEndpointConfig): EndpointTranslation
|
|
|
158
158
|
// name), never argv: argv is world-readable via `ps` and a header can carry
|
|
159
159
|
// a secret (Authorization, X-API-Key). Mirrors why the claude provider
|
|
160
160
|
// stages MCP headers to a 0600 file instead of the command line. Header
|
|
161
|
-
// names are
|
|
162
|
-
//
|
|
161
|
+
// names are emitted as quoted TOML path segments because valid HTTP token
|
|
162
|
+
// characters such as `.` must not become nested configuration paths.
|
|
163
163
|
let i = 0;
|
|
164
164
|
for (const [name, value] of Object.entries(e.headers)) {
|
|
165
165
|
const headerEnv = `${CODEX_CUSTOM_HEADER_ENV_PREFIX}${i++}`;
|
|
166
166
|
env[headerEnv] = value;
|
|
167
|
-
set(`model_providers.${id}.env_http_headers.${name}`, headerEnv);
|
|
167
|
+
set(`model_providers.${id}.env_http_headers.${JSON.stringify(name)}`, headerEnv);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
// modelMap intentionally ignored — Codex has no tier aliases.
|