@gajae-code/coding-agent 0.1.2 → 0.1.3
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 +12 -1
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -4
- package/dist/types/session/agent-session.d.ts +3 -9
- package/package.json +9 -9
- package/src/config/model-registry.ts +4 -0
- package/src/config/model-resolver.ts +5 -1
- package/src/defaults/gjc/skills/team/SKILL.md +1 -0
- package/src/gjc-runtime/team-runtime.ts +80 -1
- package/src/main.ts +1 -0
- package/src/modes/components/model-selector.ts +108 -8
- package/src/modes/interactive-mode.ts +34 -22
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +9 -3
- package/src/tools/image-gen.ts +19 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.3] - 2026-05-28
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Released the current dev branch fixes with refreshed 0.1.3 package metadata.
|
|
10
|
+
|
|
11
|
+
## [0.1.2] - 2026-05-28
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Updated package metadata for the Gajae Code npm publication.
|
|
16
|
+
|
|
5
17
|
### Fixed
|
|
6
18
|
|
|
7
19
|
- Fixed slash-command autocomplete so skill command matches no longer hide built-in fuzzy candidates like `/model` while typing `/mode`.
|
|
@@ -12,7 +24,6 @@
|
|
|
12
24
|
|
|
13
25
|
- Restored `gjc team` multi-worker GJC-team parity orchestration with current-window worker panes, GJC-scoped state/API semantics, and `N:agent-type` launches.
|
|
14
26
|
- Ported GJC team worker-worktree integration parity so `status`/`resume` auto-checkpoint dirty workers, merge or cherry-pick worker commits, cross-rebase idle workers, and record conflicts under `.gjc` integration artifacts.
|
|
15
|
-
- Restored `gjc team` multi-worker OMX-parity orchestration with current-window worker panes, GJC-scoped state/API semantics, and `N:agent-type` launches.
|
|
16
27
|
|
|
17
28
|
### Added
|
|
18
29
|
|
|
@@ -327,6 +327,7 @@ export declare class ModelRegistry {
|
|
|
327
327
|
* Check if a model is using OAuth credentials (subscription).
|
|
328
328
|
*/
|
|
329
329
|
isUsingOAuth(model: Model<Api>): boolean;
|
|
330
|
+
getSessionCredentialType(provider: string, sessionId?: string): "api_key" | "oauth" | undefined;
|
|
330
331
|
/**
|
|
331
332
|
* Remove custom API/OAuth registrations for a specific extension source.
|
|
332
333
|
*/
|
|
@@ -7,9 +7,12 @@ import { type ModelRegistry, type ModelRole } from "./model-registry";
|
|
|
7
7
|
import type { Settings } from "./settings";
|
|
8
8
|
/** Default model IDs for each known provider */
|
|
9
9
|
export declare const defaultModelPerProvider: Record<KnownProvider, string>;
|
|
10
|
-
export interface
|
|
10
|
+
export interface ScopedModelSelection {
|
|
11
11
|
model: Model<Api>;
|
|
12
12
|
thinkingLevel?: ThinkingLevel;
|
|
13
|
+
explicitThinkingLevel?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface ScopedModel extends ScopedModelSelection {
|
|
13
16
|
explicitThinkingLevel: boolean;
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
@@ -3,6 +3,7 @@ export type GjcTeamTaskStatus = "pending" | "blocked" | "in_progress" | "complet
|
|
|
3
3
|
export type GjcWorkerStatusState = "idle" | "working" | "blocked" | "done" | "failed" | "draining" | "unknown";
|
|
4
4
|
export declare const GJC_TEAM_DEFAULT_WORKERS = 3;
|
|
5
5
|
export declare const GJC_TEAM_MAX_WORKERS = 20;
|
|
6
|
+
export type GjcTeamWorkerCli = "gjc";
|
|
6
7
|
export interface GjcTeamLeader {
|
|
7
8
|
session_id: string;
|
|
8
9
|
pane_id: string;
|
|
@@ -71,6 +72,7 @@ export interface GjcTeamConfig {
|
|
|
71
72
|
max_workers: number;
|
|
72
73
|
state_root: string;
|
|
73
74
|
worker_command: string;
|
|
75
|
+
worker_cli_plan: GjcTeamWorkerCli[];
|
|
74
76
|
tmux_command: string;
|
|
75
77
|
tmux_session: string;
|
|
76
78
|
tmux_session_name: string;
|
|
@@ -138,6 +140,9 @@ export interface GjcTeamMailboxMessage {
|
|
|
138
140
|
delivered_at?: string;
|
|
139
141
|
notified_at?: string;
|
|
140
142
|
}
|
|
143
|
+
export declare function resolveGjcTeamWorkerCli(env?: NodeJS.ProcessEnv): GjcTeamWorkerCli;
|
|
144
|
+
export declare function resolveGjcTeamWorkerCliPlan(workerCount: number, env?: NodeJS.ProcessEnv): GjcTeamWorkerCli[];
|
|
145
|
+
export declare function translateGjcWorkerLaunchArgsForCli(workerCli: GjcTeamWorkerCli, args: string[]): string[];
|
|
141
146
|
interface GjcTeamEvent {
|
|
142
147
|
event_id: string;
|
|
143
148
|
ts: string;
|
|
@@ -2,11 +2,9 @@ import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
2
2
|
import { type Model } from "@gajae-code/ai";
|
|
3
3
|
import { Container, Input, type TUI } from "@gajae-code/tui";
|
|
4
4
|
import type { GjcModelAssignmentTargetId, ModelRegistry } from "../../config/model-registry";
|
|
5
|
+
import { type ScopedModelSelection } from "../../config/model-resolver";
|
|
5
6
|
import type { Settings } from "../../config/settings";
|
|
6
|
-
|
|
7
|
-
model: Model;
|
|
8
|
-
thinkingLevel?: ThinkingLevel;
|
|
9
|
-
}
|
|
7
|
+
type ScopedModelItem = ScopedModelSelection;
|
|
10
8
|
/**
|
|
11
9
|
* Component that renders a canonical model selector with provider tabs.
|
|
12
10
|
* - Tab/Arrow Left/Right: Switch between provider tabs
|
|
@@ -132,6 +132,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
132
132
|
cancelPendingSubmission(): boolean;
|
|
133
133
|
markPendingSubmissionStarted(input: SubmittedUserInput): boolean;
|
|
134
134
|
finishPendingSubmission(input: SubmittedUserInput): void;
|
|
135
|
+
updateEditorChrome(): void;
|
|
135
136
|
updateEditorBorderColor(): void;
|
|
136
137
|
updateEditorTopBorder(): void;
|
|
137
138
|
rebuildChatFromMessages(): void;
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type AgentTelemetryConfig, type ThinkingLevel } from "@gajae-code/agent
|
|
|
2
2
|
import { type Model } from "@gajae-code/ai";
|
|
3
3
|
import { type Rule } from "./capability/rule";
|
|
4
4
|
import { ModelRegistry } from "./config/model-registry";
|
|
5
|
+
import { type ScopedModelSelection } from "./config/model-resolver";
|
|
5
6
|
import { type PromptTemplate } from "./config/prompt-templates";
|
|
6
7
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
7
8
|
import "./discovery";
|
|
@@ -40,10 +41,7 @@ export interface CreateAgentSessionOptions {
|
|
|
40
41
|
/** Thinking selector. Default: from settings, else unset */
|
|
41
42
|
thinkingLevel?: ThinkingLevel;
|
|
42
43
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
43
|
-
scopedModels?:
|
|
44
|
-
model: Model;
|
|
45
|
-
thinkingLevel?: ThinkingLevel;
|
|
46
|
-
}>;
|
|
44
|
+
scopedModels?: ScopedModelSelection[];
|
|
47
45
|
/** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
|
|
48
46
|
systemPrompt?: string[] | ((defaultPrompt: string[]) => string[]);
|
|
49
47
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
@@ -18,7 +18,7 @@ import type { AssistantMessage, Effort, ImageContent, Message, MessageAttributio
|
|
|
18
18
|
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
|
|
19
19
|
import type { Rule } from "../capability/rule";
|
|
20
20
|
import { type ModelRegistry } from "../config/model-registry";
|
|
21
|
-
import { type ResolvedModelRoleValue } from "../config/model-resolver";
|
|
21
|
+
import { type ResolvedModelRoleValue, type ScopedModelSelection } from "../config/model-resolver";
|
|
22
22
|
import { type PromptTemplate } from "../config/prompt-templates";
|
|
23
23
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
24
24
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
@@ -119,10 +119,7 @@ export interface AgentSessionConfig {
|
|
|
119
119
|
sessionManager: SessionManager;
|
|
120
120
|
settings: Settings;
|
|
121
121
|
/** Models to cycle through with Ctrl+P (from --models flag) */
|
|
122
|
-
scopedModels?:
|
|
123
|
-
model: Model;
|
|
124
|
-
thinkingLevel?: ThinkingLevel;
|
|
125
|
-
}>;
|
|
122
|
+
scopedModels?: ScopedModelSelection[];
|
|
126
123
|
/** Initial session thinking selector. */
|
|
127
124
|
thinkingLevel?: ThinkingLevel;
|
|
128
125
|
/** Prompt templates for expansion */
|
|
@@ -446,10 +443,7 @@ export declare class AgentSession {
|
|
|
446
443
|
/** Current session display name, if set */
|
|
447
444
|
get sessionName(): string | undefined;
|
|
448
445
|
/** Scoped models for cycling (from --models flag) */
|
|
449
|
-
get scopedModels(): ReadonlyArray<
|
|
450
|
-
model: Model;
|
|
451
|
-
thinkingLevel?: ThinkingLevel;
|
|
452
|
-
}>;
|
|
446
|
+
get scopedModels(): ReadonlyArray<ScopedModelSelection>;
|
|
453
447
|
/** Prompt templates */
|
|
454
448
|
getPlanModeState(): PlanModeState | undefined;
|
|
455
449
|
setPlanModeState(state: PlanModeState | undefined): void;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
|
-
"homepage": "https://gajae
|
|
7
|
-
"author": "
|
|
6
|
+
"homepage": "https://gaebal-gajae.dev",
|
|
7
|
+
"author": "Yeachan-Heo",
|
|
8
8
|
"contributors": [
|
|
9
9
|
"Mario Zechner"
|
|
10
10
|
],
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
49
49
|
"@babel/parser": "^7.29.3",
|
|
50
50
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@gajae-code/stats": "0.1.
|
|
52
|
-
"@gajae-code/agent-core": "0.1.
|
|
53
|
-
"@gajae-code/ai": "0.1.
|
|
54
|
-
"@gajae-code/natives": "0.1.
|
|
55
|
-
"@gajae-code/tui": "0.1.
|
|
56
|
-
"@gajae-code/utils": "0.1.
|
|
51
|
+
"@gajae-code/stats": "0.1.3",
|
|
52
|
+
"@gajae-code/agent-core": "0.1.3",
|
|
53
|
+
"@gajae-code/ai": "0.1.3",
|
|
54
|
+
"@gajae-code/natives": "0.1.3",
|
|
55
|
+
"@gajae-code/tui": "0.1.3",
|
|
56
|
+
"@gajae-code/utils": "0.1.3",
|
|
57
57
|
"@puppeteer/browsers": "^2.13.0",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
|
59
59
|
"@xterm/headless": "^6.0.0",
|
|
@@ -2170,6 +2170,10 @@ export class ModelRegistry {
|
|
|
2170
2170
|
return this.authStorage.hasOAuth(model.provider);
|
|
2171
2171
|
}
|
|
2172
2172
|
|
|
2173
|
+
getSessionCredentialType(provider: string, sessionId?: string): "api_key" | "oauth" | undefined {
|
|
2174
|
+
return this.authStorage.getSessionCredentialType(provider, sessionId);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2173
2177
|
#clearRuntimeProviderState(providerName: string): void {
|
|
2174
2178
|
this.#runtimeProviderApiKeys.delete(providerName);
|
|
2175
2179
|
this.#runtimeProviderOverrides.delete(providerName);
|
|
@@ -23,9 +23,13 @@ import type { Settings } from "./settings";
|
|
|
23
23
|
/** Default model IDs for each known provider */
|
|
24
24
|
export const defaultModelPerProvider: Record<KnownProvider, string> = DEFAULT_MODEL_PER_PROVIDER;
|
|
25
25
|
|
|
26
|
-
export interface
|
|
26
|
+
export interface ScopedModelSelection {
|
|
27
27
|
model: Model<Api>;
|
|
28
28
|
thinkingLevel?: ThinkingLevel;
|
|
29
|
+
explicitThinkingLevel?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ScopedModel extends ScopedModelSelection {
|
|
29
33
|
explicitThinkingLevel: boolean;
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -155,6 +155,7 @@ Important:
|
|
|
155
155
|
|
|
156
156
|
- Leader remains in the existing left pane.
|
|
157
157
|
- Worker panes are independent full GJC worker CLI sessions on the right side of a leader-left/worker-right split.
|
|
158
|
+
- Worker CLI selection is teammate-only: `GJC_TEAM_WORKER_CLI` and `GJC_TEAM_WORKER_CLI_MAP` accept only `auto` or `gjc`; legacy/provider values such as `codex`, `claude`, or `gemini` are rejected before launch.
|
|
158
159
|
- The worker may run in a dedicated git worktree (`gjc team --worktree[=<name>]`) while sharing the team state root.
|
|
159
160
|
- `shutdown` kills only the recorded worker pane after confirming it still belongs to the stored tmux target and is not the leader pane. It never kills the tmux session.
|
|
160
161
|
|
|
@@ -8,6 +8,11 @@ export type GjcWorkerStatusState = "idle" | "working" | "blocked" | "done" | "fa
|
|
|
8
8
|
|
|
9
9
|
export const GJC_TEAM_DEFAULT_WORKERS = 3;
|
|
10
10
|
export const GJC_TEAM_MAX_WORKERS = 20;
|
|
11
|
+
const GJC_TEAM_WORKER_CLI_ENV = "GJC_TEAM_WORKER_CLI";
|
|
12
|
+
const GJC_TEAM_WORKER_CLI_MAP_ENV = "GJC_TEAM_WORKER_CLI_MAP";
|
|
13
|
+
|
|
14
|
+
export type GjcTeamWorkerCli = "gjc";
|
|
15
|
+
type GjcTeamWorkerCliMode = "auto" | GjcTeamWorkerCli;
|
|
11
16
|
|
|
12
17
|
export interface GjcTeamLeader {
|
|
13
18
|
session_id: string;
|
|
@@ -75,6 +80,7 @@ export interface GjcTeamConfig {
|
|
|
75
80
|
max_workers: number;
|
|
76
81
|
state_root: string;
|
|
77
82
|
worker_command: string;
|
|
83
|
+
worker_cli_plan: GjcTeamWorkerCli[];
|
|
78
84
|
tmux_command: string;
|
|
79
85
|
tmux_session: string;
|
|
80
86
|
tmux_session_name: string;
|
|
@@ -159,6 +165,70 @@ export interface GjcTeamMailboxMessage {
|
|
|
159
165
|
interface FsError {
|
|
160
166
|
code?: string;
|
|
161
167
|
}
|
|
168
|
+
|
|
169
|
+
function normalizeGjcTeamWorkerCliMode(
|
|
170
|
+
raw: string | undefined,
|
|
171
|
+
sourceEnv = GJC_TEAM_WORKER_CLI_ENV,
|
|
172
|
+
): GjcTeamWorkerCliMode {
|
|
173
|
+
const normalized = String(raw ?? "auto")
|
|
174
|
+
.trim()
|
|
175
|
+
.toLowerCase();
|
|
176
|
+
if (normalized === "" || normalized === "auto") return "auto";
|
|
177
|
+
if (normalized === "gjc") return "gjc";
|
|
178
|
+
if (normalized === "codex" || normalized === "claude" || normalized === "gemini") {
|
|
179
|
+
throw new Error(`Unsupported ${sourceEnv} value "${raw}". GJC team launches GJC teammate sessions only.`);
|
|
180
|
+
}
|
|
181
|
+
throw new Error(`Invalid ${sourceEnv} value "${raw}". Expected: auto or gjc`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function resolveGjcTeamWorkerCli(env: NodeJS.ProcessEnv = process.env): GjcTeamWorkerCli {
|
|
185
|
+
const mode = normalizeGjcTeamWorkerCliMode(env[GJC_TEAM_WORKER_CLI_ENV]);
|
|
186
|
+
return mode === "auto" ? "gjc" : mode;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function resolveGjcTeamWorkerCliPlan(
|
|
190
|
+
workerCount: number,
|
|
191
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
192
|
+
): GjcTeamWorkerCli[] {
|
|
193
|
+
if (!Number.isInteger(workerCount) || workerCount < 1) {
|
|
194
|
+
throw new Error(`workerCount must be >= 1 (got ${workerCount})`);
|
|
195
|
+
}
|
|
196
|
+
normalizeGjcTeamWorkerCliMode(env[GJC_TEAM_WORKER_CLI_ENV]);
|
|
197
|
+
const rawMap = String(env[GJC_TEAM_WORKER_CLI_MAP_ENV] ?? "").trim();
|
|
198
|
+
if (rawMap === "") {
|
|
199
|
+
const cli = resolveGjcTeamWorkerCli(env);
|
|
200
|
+
return Array.from({ length: workerCount }, () => cli);
|
|
201
|
+
}
|
|
202
|
+
const entries = rawMap.split(",").map(entry => entry.trim());
|
|
203
|
+
if (entries.length === 0 || entries.every(entry => entry.length === 0)) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} value "${env[GJC_TEAM_WORKER_CLI_MAP_ENV]}". Expected: auto or gjc`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (entries.some(entry => entry.length === 0)) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} value "${env[GJC_TEAM_WORKER_CLI_MAP_ENV]}". Empty entries are not allowed.`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (entries.length !== 1 && entries.length !== workerCount) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Invalid ${GJC_TEAM_WORKER_CLI_MAP_ENV} length ${entries.length}; expected 1 or ${workerCount} comma-separated values.`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const expanded = entries.length === 1 ? Array.from({ length: workerCount }, () => entries[0] ?? "") : entries;
|
|
219
|
+
return expanded.map(entry => {
|
|
220
|
+
const mode = normalizeGjcTeamWorkerCliMode(entry, GJC_TEAM_WORKER_CLI_MAP_ENV);
|
|
221
|
+
return mode === "auto" ? "gjc" : mode;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function translateGjcWorkerLaunchArgsForCli(workerCli: GjcTeamWorkerCli, args: string[]): string[] {
|
|
226
|
+
if (workerCli !== "gjc") {
|
|
227
|
+
throw new Error(`Unsupported team worker CLI "${workerCli}". GJC team launches GJC teammate sessions only.`);
|
|
228
|
+
}
|
|
229
|
+
return [...args];
|
|
230
|
+
}
|
|
231
|
+
|
|
162
232
|
interface GjcTmuxLeaderContext {
|
|
163
233
|
sessionName: string;
|
|
164
234
|
windowIndex: string;
|
|
@@ -341,6 +411,7 @@ async function readConfig(dir: string): Promise<GjcTeamConfig> {
|
|
|
341
411
|
tmux_target: config.tmux_target ?? config.tmux_session ?? tmuxSessionName,
|
|
342
412
|
leader_cwd: config.leader_cwd ?? config.leader.cwd,
|
|
343
413
|
team_state_root: config.team_state_root ?? config.state_root,
|
|
414
|
+
worker_cli_plan: config.worker_cli_plan ?? Array.from({ length: config.worker_count }, () => "gjc"),
|
|
344
415
|
};
|
|
345
416
|
}
|
|
346
417
|
async function readPhase(dir: string): Promise<GjcTeamPhase> {
|
|
@@ -1227,6 +1298,7 @@ export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTea
|
|
|
1227
1298
|
const env = options.env ?? process.env;
|
|
1228
1299
|
if (!Number.isInteger(options.workerCount) || options.workerCount < 1 || options.workerCount > GJC_TEAM_MAX_WORKERS)
|
|
1229
1300
|
throw new Error(`invalid_team_worker_count:${options.workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
|
|
1301
|
+
const workerCliPlan = resolveGjcTeamWorkerCliPlan(options.workerCount, env);
|
|
1230
1302
|
const stateRoot = resolveGjcTeamStateRoot(cwd, env);
|
|
1231
1303
|
const teamName = sanitizeName(options.teamName ?? makeTeamName(options.task, env));
|
|
1232
1304
|
const displayName = sanitizeName(options.teamName ?? options.task).slice(0, 30) || teamName;
|
|
@@ -1256,6 +1328,7 @@ export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTea
|
|
|
1256
1328
|
max_workers: GJC_TEAM_MAX_WORKERS,
|
|
1257
1329
|
state_root: stateRoot,
|
|
1258
1330
|
worker_command: resolveGjcWorkerCommand(cwd, env),
|
|
1331
|
+
worker_cli_plan: workerCliPlan,
|
|
1259
1332
|
tmux_command: tmuxCommand,
|
|
1260
1333
|
tmux_session: tmuxContext.sessionName,
|
|
1261
1334
|
tmux_session_name: tmuxContext.sessionName,
|
|
@@ -1279,6 +1352,7 @@ export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTea
|
|
|
1279
1352
|
tmux_session_name: config.tmux_session_name,
|
|
1280
1353
|
tmux_target: config.tmux_target,
|
|
1281
1354
|
worker_command: config.worker_command,
|
|
1355
|
+
worker_cli_plan: config.worker_cli_plan,
|
|
1282
1356
|
tmux_command: config.tmux_command,
|
|
1283
1357
|
leader: config.leader,
|
|
1284
1358
|
workers: config.workers,
|
|
@@ -1296,7 +1370,12 @@ export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTea
|
|
|
1296
1370
|
await appendTelemetry(dir, {
|
|
1297
1371
|
type: "team_runtime",
|
|
1298
1372
|
message: "Native gjc team runtime initialized",
|
|
1299
|
-
data: {
|
|
1373
|
+
data: {
|
|
1374
|
+
state_root: stateRoot,
|
|
1375
|
+
worker_command: config.worker_command,
|
|
1376
|
+
worker_cli_plan: workerCliPlan,
|
|
1377
|
+
workspace_mode: config.workspace_mode,
|
|
1378
|
+
},
|
|
1300
1379
|
});
|
|
1301
1380
|
let tmuxWorkers: GjcTeamWorker[];
|
|
1302
1381
|
try {
|
package/src/main.ts
CHANGED
|
@@ -601,6 +601,7 @@ async function buildSessionOptions(
|
|
|
601
601
|
thinkingLevel: scopedModel.explicitThinkingLevel
|
|
602
602
|
? (scopedModel.thinkingLevel ?? defaultThinkingLevel)
|
|
603
603
|
: defaultThinkingLevel,
|
|
604
|
+
explicitThinkingLevel: scopedModel.explicitThinkingLevel,
|
|
604
605
|
}));
|
|
605
606
|
}
|
|
606
607
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
2
|
-
import { type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
2
|
+
import { getSupportedEfforts, type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
5
5
|
fuzzyFilter,
|
|
@@ -14,11 +14,15 @@ import {
|
|
|
14
14
|
} from "@gajae-code/tui";
|
|
15
15
|
import type { GjcModelAssignmentTargetId, ModelRegistry } from "../../config/model-registry";
|
|
16
16
|
import { GJC_MODEL_ASSIGNMENT_TARGET_IDS, GJC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
formatModelSelectorValue,
|
|
19
|
+
resolveModelRoleValue,
|
|
20
|
+
type ScopedModelSelection,
|
|
21
|
+
} from "../../config/model-resolver";
|
|
18
22
|
import type { Settings } from "../../config/settings";
|
|
19
23
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
20
24
|
import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
|
|
21
|
-
import { getThinkingLevelMetadata } from "../../thinking";
|
|
25
|
+
import { getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
|
|
22
26
|
import { getTabBarTheme } from "../shared";
|
|
23
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
24
28
|
|
|
@@ -54,6 +58,7 @@ interface ModelItem {
|
|
|
54
58
|
model: Model;
|
|
55
59
|
selector: string;
|
|
56
60
|
thinkingLevel?: ThinkingLevel;
|
|
61
|
+
explicitThinkingLevel?: boolean;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
interface CanonicalModelItem {
|
|
@@ -66,18 +71,22 @@ interface CanonicalModelItem {
|
|
|
66
71
|
normalizedSearchText: string;
|
|
67
72
|
compactSearchText: string;
|
|
68
73
|
thinkingLevel?: ThinkingLevel;
|
|
74
|
+
explicitThinkingLevel?: boolean;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
model: Model;
|
|
73
|
-
thinkingLevel?: ThinkingLevel;
|
|
74
|
-
}
|
|
77
|
+
type ScopedModelItem = ScopedModelSelection;
|
|
75
78
|
|
|
76
79
|
interface RoleAssignment {
|
|
77
80
|
model: Model;
|
|
78
81
|
thinkingLevel: ThinkingLevel;
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
interface PendingThinkingChoice {
|
|
85
|
+
item: ModelItem | CanonicalModelItem;
|
|
86
|
+
role: GjcModelAssignmentTargetId | null;
|
|
87
|
+
levels: ThinkingLevel[];
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
type RoleSelectCallback = (
|
|
82
91
|
model: Model,
|
|
83
92
|
role: GjcModelAssignmentTargetId | null,
|
|
@@ -134,6 +143,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
134
143
|
#temporaryOnly: boolean;
|
|
135
144
|
#pendingActionItem?: ModelItem | CanonicalModelItem;
|
|
136
145
|
#selectedActionIndex: number = 0;
|
|
146
|
+
#pendingThinkingChoice?: PendingThinkingChoice;
|
|
147
|
+
#selectedThinkingIndex: number = 0;
|
|
137
148
|
|
|
138
149
|
// Tab state
|
|
139
150
|
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
|
|
@@ -349,6 +360,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
349
360
|
model: scoped.model,
|
|
350
361
|
selector: `${scoped.model.provider}/${scoped.model.id}`,
|
|
351
362
|
thinkingLevel: scoped.thinkingLevel,
|
|
363
|
+
explicitThinkingLevel: scoped.explicitThinkingLevel,
|
|
352
364
|
}));
|
|
353
365
|
} else {
|
|
354
366
|
// Reload config and cached discovery state without blocking on live provider refresh
|
|
@@ -418,6 +430,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
418
430
|
if (scopedThinkingLevel !== undefined) {
|
|
419
431
|
item.thinkingLevel = scopedThinkingLevel;
|
|
420
432
|
}
|
|
433
|
+
const scopedModel = models.find(model => `${model.model.provider}/${model.model.id}` === selectedSelector);
|
|
434
|
+
if (scopedModel?.explicitThinkingLevel !== undefined) {
|
|
435
|
+
item.explicitThinkingLevel = scopedModel.explicitThinkingLevel;
|
|
436
|
+
}
|
|
421
437
|
return item;
|
|
422
438
|
})
|
|
423
439
|
.filter((item): item is CanonicalModelItem => item !== undefined);
|
|
@@ -722,11 +738,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
722
738
|
this.#listContainer.addChild(
|
|
723
739
|
new Text(theme.fg("muted", ` Model Name: ${selected.model.name}${suffix}`), 0, 0),
|
|
724
740
|
);
|
|
725
|
-
if (this.#
|
|
741
|
+
if (this.#pendingThinkingChoice) {
|
|
742
|
+
this.#renderThinkingMenu(this.#pendingThinkingChoice);
|
|
743
|
+
} else if (this.#pendingActionItem) {
|
|
726
744
|
this.#renderActionMenu(this.#pendingActionItem);
|
|
727
745
|
}
|
|
728
746
|
}
|
|
729
747
|
}
|
|
748
|
+
|
|
730
749
|
#renderActionMenu(item: ModelItem | CanonicalModelItem): void {
|
|
731
750
|
this.#listContainer.addChild(new Spacer(1));
|
|
732
751
|
this.#listContainer.addChild(new Text(theme.fg("muted", ` Action for: ${item.model.id}`), 0, 0));
|
|
@@ -742,6 +761,24 @@ export class ModelSelectorComponent extends Container {
|
|
|
742
761
|
}
|
|
743
762
|
}
|
|
744
763
|
|
|
764
|
+
#renderThinkingMenu(choice: PendingThinkingChoice): void {
|
|
765
|
+
const targetLabel = choice.role === null ? "temporary model" : GJC_MODEL_ASSIGNMENT_TARGETS[choice.role].name;
|
|
766
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
767
|
+
this.#listContainer.addChild(
|
|
768
|
+
new Text(theme.fg("muted", ` Reasoning for ${targetLabel}: ${choice.item.model.id}`), 0, 0),
|
|
769
|
+
);
|
|
770
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
771
|
+
for (let i = 0; i < choice.levels.length; i++) {
|
|
772
|
+
const level = choice.levels[i];
|
|
773
|
+
const metadata = getThinkingLevelMetadata(level);
|
|
774
|
+
const prefix = i === this.#selectedThinkingIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
775
|
+
const label = `${metadata.label} — ${metadata.description}`;
|
|
776
|
+
this.#listContainer.addChild(
|
|
777
|
+
new Text(`${prefix}${i === this.#selectedThinkingIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
745
782
|
#getCurrentRoleThinkingLevel(role: string): ThinkingLevel {
|
|
746
783
|
return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
|
|
747
784
|
}
|
|
@@ -753,6 +790,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
753
790
|
}
|
|
754
791
|
|
|
755
792
|
handleInput(keyData: string): void {
|
|
793
|
+
if (this.#pendingThinkingChoice) {
|
|
794
|
+
this.#handleThinkingMenuInput(keyData);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
756
797
|
if (this.#pendingActionItem) {
|
|
757
798
|
this.#handleActionMenuInput(keyData);
|
|
758
799
|
return;
|
|
@@ -839,12 +880,50 @@ export class ModelSelectorComponent extends Container {
|
|
|
839
880
|
}
|
|
840
881
|
}
|
|
841
882
|
|
|
883
|
+
#handleThinkingMenuInput(keyData: string): void {
|
|
884
|
+
const choice = this.#pendingThinkingChoice;
|
|
885
|
+
if (!choice) return;
|
|
886
|
+
if (matchesKey(keyData, "up")) {
|
|
887
|
+
this.#selectedThinkingIndex =
|
|
888
|
+
this.#selectedThinkingIndex === 0 ? choice.levels.length - 1 : this.#selectedThinkingIndex - 1;
|
|
889
|
+
this.#updateList();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (matchesKey(keyData, "down")) {
|
|
893
|
+
this.#selectedThinkingIndex = (this.#selectedThinkingIndex + 1) % choice.levels.length;
|
|
894
|
+
this.#updateList();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
898
|
+
const level = choice.levels[this.#selectedThinkingIndex];
|
|
899
|
+
if (!level) return;
|
|
900
|
+
this.#pendingThinkingChoice = undefined;
|
|
901
|
+
this.#handleSelect(choice.item, choice.role, level);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
905
|
+
this.#pendingThinkingChoice = undefined;
|
|
906
|
+
if (choice.role !== null) {
|
|
907
|
+
this.#pendingActionItem = choice.item;
|
|
908
|
+
this.#selectedActionIndex = Math.max(0, GJC_MODEL_ASSIGNMENT_TARGET_IDS.indexOf(choice.role));
|
|
909
|
+
}
|
|
910
|
+
this.#updateList();
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
842
914
|
#handleSelect(
|
|
843
915
|
item: ModelItem | CanonicalModelItem,
|
|
844
916
|
role: GjcModelAssignmentTargetId | null,
|
|
845
917
|
thinkingLevel?: ThinkingLevel,
|
|
846
918
|
): void {
|
|
847
919
|
const itemThinkingLevel = thinkingLevel ?? item.thinkingLevel;
|
|
920
|
+
const hasExplicitThinkingChoice = thinkingLevel !== undefined || item.explicitThinkingLevel === true;
|
|
921
|
+
if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model)) {
|
|
922
|
+
this.#pendingThinkingChoice = { item, role, levels: getSelectableThinkingLevels(item.model) };
|
|
923
|
+
this.#selectedThinkingIndex = 0;
|
|
924
|
+
this.#updateList();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
848
927
|
|
|
849
928
|
// For temporary role, don't save to settings - just notify caller
|
|
850
929
|
if (role === null) {
|
|
@@ -871,6 +950,27 @@ export class ModelSelectorComponent extends Container {
|
|
|
871
950
|
}
|
|
872
951
|
}
|
|
873
952
|
|
|
953
|
+
function requiresExplicitThinkingChoice(model: Model): boolean {
|
|
954
|
+
return model.reasoning === true && (model.provider === "openai" || model.provider === "openai-codex");
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
|
|
958
|
+
const levels: ThinkingLevel[] = [ThinkingLevel.Off];
|
|
959
|
+
let efforts: readonly string[];
|
|
960
|
+
try {
|
|
961
|
+
efforts = getSupportedEfforts(model);
|
|
962
|
+
} catch {
|
|
963
|
+
return levels;
|
|
964
|
+
}
|
|
965
|
+
for (const effort of efforts) {
|
|
966
|
+
const level = parseThinkingLevel(effort);
|
|
967
|
+
if (level && !levels.includes(level)) {
|
|
968
|
+
levels.push(level);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return levels;
|
|
972
|
+
}
|
|
973
|
+
|
|
874
974
|
/** Extract the first version number from a model ID (e.g. "gemini-2.5-pro" → 2.5, "Anthropic model-sonnet-4-6" → 4.6). */
|
|
875
975
|
function extractVersionNumber(id: string): number {
|
|
876
976
|
// Dot-separated version: "gemini-2.5-pro" → 2.5
|
|
@@ -121,9 +121,14 @@ const HINT_SHIMMER_PALETTE: ShimmerPalette = {
|
|
|
121
121
|
};
|
|
122
122
|
|
|
123
123
|
function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
124
|
-
editor.setBorderVisible(
|
|
125
|
-
editor.
|
|
124
|
+
editor.setBorderVisible(true);
|
|
125
|
+
editor.setBorderStyle("sharp");
|
|
126
|
+
editor.setClosedBorderBox(true);
|
|
127
|
+
editor.setPromptGutter(undefined);
|
|
128
|
+
editor.setInputPrefix(`${theme.fg("accent", ">")} `);
|
|
129
|
+
editor.setPlaceholder("Type your message...");
|
|
126
130
|
editor.setPaddingX(1);
|
|
131
|
+
editor.setTopBorder(undefined);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
interface WorkingMessageAccent {
|
|
@@ -376,7 +381,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
376
381
|
this.#syncEditorMaxHeight();
|
|
377
382
|
this.#resizeHandler = () => {
|
|
378
383
|
this.#syncEditorMaxHeight();
|
|
379
|
-
this.
|
|
384
|
+
this.updateEditorChrome();
|
|
380
385
|
};
|
|
381
386
|
process.stdout.on("resize", this.#resizeHandler);
|
|
382
387
|
try {
|
|
@@ -500,7 +505,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
500
505
|
this.ui.addChild(this.statusContainer);
|
|
501
506
|
this.ui.addChild(this.todoContainer);
|
|
502
507
|
this.ui.addChild(this.btwContainer);
|
|
503
|
-
this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer
|
|
508
|
+
this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer chrome is rendered by the editor.
|
|
504
509
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
505
510
|
this.ui.addChild(this.editorContainer);
|
|
506
511
|
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
@@ -526,7 +531,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
526
531
|
this.ui.start();
|
|
527
532
|
pushTerminalTitle();
|
|
528
533
|
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
529
|
-
this.
|
|
534
|
+
this.updateEditorChrome();
|
|
530
535
|
this.#syncEditorMaxHeight();
|
|
531
536
|
this.isInitialized = true;
|
|
532
537
|
this.ui.requestRender(true);
|
|
@@ -544,7 +549,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
544
549
|
const draft = await this.sessionManager.consumeDraft();
|
|
545
550
|
if (draft && !this.editor.getText()) {
|
|
546
551
|
this.editor.setText(draft);
|
|
547
|
-
this.
|
|
552
|
+
this.updateEditorChrome();
|
|
548
553
|
this.ui.requestRender();
|
|
549
554
|
}
|
|
550
555
|
} catch (err) {
|
|
@@ -564,7 +569,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
564
569
|
clearRenderCache();
|
|
565
570
|
configureDefaultComposerChrome(this.editor);
|
|
566
571
|
this.ui.invalidate();
|
|
567
|
-
this.
|
|
572
|
+
this.updateEditorChrome();
|
|
568
573
|
this.ui.requestRender();
|
|
569
574
|
});
|
|
570
575
|
|
|
@@ -577,12 +582,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
577
582
|
|
|
578
583
|
// Set up git branch watcher
|
|
579
584
|
this.statusLine.watchBranch(() => {
|
|
580
|
-
this.
|
|
585
|
+
this.updateEditorChrome();
|
|
581
586
|
this.ui.requestRender();
|
|
582
587
|
});
|
|
583
588
|
|
|
584
589
|
// Initial top border update
|
|
585
|
-
this.
|
|
590
|
+
this.updateEditorChrome();
|
|
586
591
|
}
|
|
587
592
|
|
|
588
593
|
/** Reload slash commands and autocomplete for the provided working directory. */
|
|
@@ -749,7 +754,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
749
754
|
this.loopLimit = undefined;
|
|
750
755
|
this.#cancelLoopAutoSubmit();
|
|
751
756
|
this.statusLine.setLoopModeStatus(undefined);
|
|
752
|
-
this.
|
|
757
|
+
this.updateEditorChrome();
|
|
753
758
|
this.ui.requestRender();
|
|
754
759
|
if (wasEnabled) {
|
|
755
760
|
this.showStatus(message);
|
|
@@ -780,7 +785,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
780
785
|
this.loopPrompt = undefined;
|
|
781
786
|
this.loopLimit = createLoopLimitRuntime(parsedLimit);
|
|
782
787
|
this.statusLine.setLoopModeStatus({ enabled: true });
|
|
783
|
-
this.
|
|
788
|
+
this.updateEditorChrome();
|
|
784
789
|
this.ui.requestRender();
|
|
785
790
|
const limitSuffix = parsedLimit ? ` Limited to ${describeLoopLimit(parsedLimit)}.` : "";
|
|
786
791
|
const remainingSuffix = this.loopLimit ? ` ${describeLoopLimitRuntime(this.loopLimit)}.` : "";
|
|
@@ -874,7 +879,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
874
879
|
this.rebuildChatFromMessages();
|
|
875
880
|
this.editor.setText(submission.text);
|
|
876
881
|
}
|
|
877
|
-
this.
|
|
882
|
+
this.updateEditorChrome();
|
|
878
883
|
this.ui.requestRender();
|
|
879
884
|
return true;
|
|
880
885
|
}
|
|
@@ -921,7 +926,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
921
926
|
this.editor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
922
927
|
}
|
|
923
928
|
|
|
924
|
-
|
|
929
|
+
updateEditorChrome(): void {
|
|
925
930
|
if (this.isBashMode) {
|
|
926
931
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
927
932
|
} else if (this.isPythonMode) {
|
|
@@ -938,13 +943,21 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
938
943
|
this.editor.borderColor = theme.getThinkingBorderColor(level);
|
|
939
944
|
}
|
|
940
945
|
}
|
|
941
|
-
this
|
|
946
|
+
this.#setComposerTopBorder();
|
|
942
947
|
this.ui.requestRender();
|
|
943
948
|
}
|
|
944
949
|
|
|
950
|
+
updateEditorBorderColor(): void {
|
|
951
|
+
this.updateEditorChrome();
|
|
952
|
+
}
|
|
953
|
+
|
|
945
954
|
updateEditorTopBorder(): void {
|
|
946
|
-
|
|
947
|
-
|
|
955
|
+
this.#setComposerTopBorder();
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
#setComposerTopBorder(): void {
|
|
959
|
+
// Keep the composer as a plain closed input rectangle; status-line
|
|
960
|
+
// rendering stays outside the input area.
|
|
948
961
|
this.editor.setTopBorder(undefined);
|
|
949
962
|
}
|
|
950
963
|
|
|
@@ -1048,7 +1061,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1048
1061
|
}
|
|
1049
1062
|
: undefined;
|
|
1050
1063
|
this.statusLine.setPlanModeStatus(status);
|
|
1051
|
-
this.
|
|
1064
|
+
this.updateEditorChrome();
|
|
1052
1065
|
this.ui.requestRender();
|
|
1053
1066
|
}
|
|
1054
1067
|
|
|
@@ -1058,7 +1071,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1058
1071
|
? { enabled: this.goalModeEnabled, paused: this.goalModePaused }
|
|
1059
1072
|
: undefined;
|
|
1060
1073
|
this.statusLine.setGoalModeStatus(status);
|
|
1061
|
-
this.
|
|
1074
|
+
this.updateEditorChrome();
|
|
1062
1075
|
this.ui.requestRender();
|
|
1063
1076
|
}
|
|
1064
1077
|
|
|
@@ -1650,7 +1663,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1650
1663
|
const applied = await this.sessionManager.setSessionName(seededName, "auto");
|
|
1651
1664
|
if (applied) {
|
|
1652
1665
|
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
1653
|
-
this.
|
|
1666
|
+
this.updateEditorChrome();
|
|
1654
1667
|
}
|
|
1655
1668
|
}
|
|
1656
1669
|
|
|
@@ -2121,8 +2134,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2121
2134
|
logger.warn("Failed to refresh slash command state for custom editor", { error: String(error) });
|
|
2122
2135
|
});
|
|
2123
2136
|
|
|
2124
|
-
this.
|
|
2125
|
-
this.updateEditorTopBorder();
|
|
2137
|
+
this.updateEditorChrome();
|
|
2126
2138
|
this.ui.requestRender();
|
|
2127
2139
|
}
|
|
2128
2140
|
|
|
@@ -2417,7 +2429,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2417
2429
|
} else {
|
|
2418
2430
|
this.#cleanupMicAnimation();
|
|
2419
2431
|
}
|
|
2420
|
-
this.
|
|
2432
|
+
this.updateEditorChrome();
|
|
2421
2433
|
this.ui.requestRender();
|
|
2422
2434
|
},
|
|
2423
2435
|
});
|
package/src/sdk.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
parseModelString,
|
|
41
41
|
resolveAllowedModels,
|
|
42
42
|
resolveModelRoleValue,
|
|
43
|
+
type ScopedModelSelection,
|
|
43
44
|
} from "./config/model-resolver";
|
|
44
45
|
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
|
|
45
46
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
@@ -230,7 +231,7 @@ export interface CreateAgentSessionOptions {
|
|
|
230
231
|
/** Thinking selector. Default: from settings, else unset */
|
|
231
232
|
thinkingLevel?: ThinkingLevel;
|
|
232
233
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
233
|
-
scopedModels?:
|
|
234
|
+
scopedModels?: ScopedModelSelection[];
|
|
234
235
|
|
|
235
236
|
/** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
|
|
236
237
|
systemPrompt?: string[] | ((defaultPrompt: string[]) => string[]);
|
|
@@ -1760,6 +1761,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1760
1761
|
}
|
|
1761
1762
|
return key;
|
|
1762
1763
|
},
|
|
1764
|
+
getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, agent.sessionId),
|
|
1763
1765
|
streamFn: (streamModel, context, streamOptions) =>
|
|
1764
1766
|
streamSimple(streamModel, context, {
|
|
1765
1767
|
...streamOptions,
|
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
parseModelString,
|
|
96
96
|
type ResolvedModelRoleValue,
|
|
97
97
|
resolveModelRoleValue,
|
|
98
|
+
type ScopedModelSelection,
|
|
98
99
|
} from "../config/model-resolver";
|
|
99
100
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
100
101
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
@@ -258,7 +259,7 @@ export interface AgentSessionConfig {
|
|
|
258
259
|
sessionManager: SessionManager;
|
|
259
260
|
settings: Settings;
|
|
260
261
|
/** Models to cycle through with Ctrl+P (from --models flag) */
|
|
261
|
-
scopedModels?:
|
|
262
|
+
scopedModels?: ScopedModelSelection[];
|
|
262
263
|
/** Initial session thinking selector. */
|
|
263
264
|
thinkingLevel?: ThinkingLevel;
|
|
264
265
|
/** Prompt templates for expansion */
|
|
@@ -748,7 +749,7 @@ export class AgentSession {
|
|
|
748
749
|
|
|
749
750
|
readonly configWarnings: string[] = [];
|
|
750
751
|
|
|
751
|
-
#scopedModels:
|
|
752
|
+
#scopedModels: ScopedModelSelection[];
|
|
752
753
|
#thinkingLevel: ThinkingLevel | undefined;
|
|
753
754
|
#promptTemplates: PromptTemplate[];
|
|
754
755
|
#slashCommands: FileSlashCommand[];
|
|
@@ -3749,7 +3750,7 @@ export class AgentSession {
|
|
|
3749
3750
|
}
|
|
3750
3751
|
|
|
3751
3752
|
/** Scoped models for cycling (from --models flag) */
|
|
3752
|
-
get scopedModels(): ReadonlyArray<
|
|
3753
|
+
get scopedModels(): ReadonlyArray<ScopedModelSelection> {
|
|
3753
3754
|
return this.#scopedModels;
|
|
3754
3755
|
}
|
|
3755
3756
|
|
|
@@ -6445,6 +6446,7 @@ export class AgentSession {
|
|
|
6445
6446
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
6446
6447
|
convertToLlm,
|
|
6447
6448
|
telemetry,
|
|
6449
|
+
authCredentialType: this.#modelRegistry.getSessionCredentialType(candidate.provider, this.sessionId),
|
|
6448
6450
|
});
|
|
6449
6451
|
} catch (error) {
|
|
6450
6452
|
if (!this.#isCompactionAuthFailure(error)) {
|
|
@@ -6700,6 +6702,10 @@ export class AgentSession {
|
|
|
6700
6702
|
initiatorOverride: "agent",
|
|
6701
6703
|
convertToLlm,
|
|
6702
6704
|
telemetry,
|
|
6705
|
+
authCredentialType: this.#modelRegistry.getSessionCredentialType(
|
|
6706
|
+
candidate.provider,
|
|
6707
|
+
this.sessionId,
|
|
6708
|
+
),
|
|
6703
6709
|
});
|
|
6704
6710
|
break;
|
|
6705
6711
|
} catch (error) {
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -44,6 +44,7 @@ interface ImageApiKey {
|
|
|
44
44
|
apiKey: string;
|
|
45
45
|
projectId?: string;
|
|
46
46
|
model?: Model;
|
|
47
|
+
authCredentialType?: "api_key" | "oauth";
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const responseModalitySchema = z.enum(["IMAGE", "TEXT"] as const);
|
|
@@ -441,6 +442,7 @@ async function findOpenAIHostedImageCredentials(
|
|
|
441
442
|
provider: getOpenAIHostedImageProvider(activeModel),
|
|
442
443
|
apiKey,
|
|
443
444
|
model: activeModel,
|
|
445
|
+
authCredentialType: modelRegistry.getSessionCredentialType?.(activeModel.provider, sessionId),
|
|
444
446
|
};
|
|
445
447
|
}
|
|
446
448
|
|
|
@@ -700,16 +702,21 @@ function getOpenAIResponseErrorMessage(rawText: string): string {
|
|
|
700
702
|
}
|
|
701
703
|
}
|
|
702
704
|
|
|
703
|
-
function getOpenAIBaseUrl(model: Model): string {
|
|
704
|
-
|
|
705
|
-
model.
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
705
|
+
function getOpenAIBaseUrl(model: Model, authCredentialType?: "api_key" | "oauth"): string {
|
|
706
|
+
if (model.api === "openai-codex-responses" || model.provider === "openai-codex") {
|
|
707
|
+
return (model.baseUrl || CODEX_BASE_URL).replace(/\/+$/, "");
|
|
708
|
+
}
|
|
709
|
+
if (authCredentialType === "oauth") return DEFAULT_OPENAI_BASE_URL;
|
|
710
|
+
const envBaseUrl = $env.OPENAI_BASE_URL?.trim();
|
|
711
|
+
const configuredBaseUrl = model.baseUrl?.trim();
|
|
712
|
+
if (envBaseUrl && (!configuredBaseUrl || configuredBaseUrl.toLowerCase().includes("api.openai.com"))) {
|
|
713
|
+
return envBaseUrl.replace(/\/+$/, "");
|
|
714
|
+
}
|
|
715
|
+
return (configuredBaseUrl || envBaseUrl || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
|
|
709
716
|
}
|
|
710
717
|
|
|
711
|
-
function getOpenAIResponsesUrl(model: Model): string {
|
|
712
|
-
const baseUrl = getOpenAIBaseUrl(model);
|
|
718
|
+
function getOpenAIResponsesUrl(model: Model, authCredentialType?: "api_key" | "oauth"): string {
|
|
719
|
+
const baseUrl = getOpenAIBaseUrl(model, authCredentialType);
|
|
713
720
|
if (model.api !== "openai-codex-responses" && model.provider !== "openai-codex") {
|
|
714
721
|
return `${baseUrl}/responses`;
|
|
715
722
|
}
|
|
@@ -782,11 +789,12 @@ async function generateOpenAIHostedImage(
|
|
|
782
789
|
inputImages: InlineImageData[],
|
|
783
790
|
signal: AbortSignal | undefined,
|
|
784
791
|
sessionId: string | undefined,
|
|
792
|
+
options?: { authCredentialType?: "api_key" | "oauth" },
|
|
785
793
|
): Promise<OpenAIHostedImageResult> {
|
|
786
794
|
const promptText = assemblePrompt(params);
|
|
787
795
|
const stream = model.api === "openai-codex-responses" || model.provider === "openai-codex";
|
|
788
796
|
const requestBody = buildOpenAIHostedImageRequest(model, promptText, params, inputImages, stream);
|
|
789
|
-
const response = await fetch(getOpenAIResponsesUrl(model), {
|
|
797
|
+
const response = await fetch(getOpenAIResponsesUrl(model, options?.authCredentialType), {
|
|
790
798
|
method: "POST",
|
|
791
799
|
headers: buildOpenAIImageHeaders(model, apiKey, sessionId),
|
|
792
800
|
body: JSON.stringify(requestBody),
|
|
@@ -946,6 +954,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
946
954
|
resolvedImages,
|
|
947
955
|
requestSignal,
|
|
948
956
|
sessionId,
|
|
957
|
+
{ authCredentialType: apiKey.authCredentialType },
|
|
949
958
|
);
|
|
950
959
|
|
|
951
960
|
if (parsed.images.length === 0) {
|
|
@@ -1075,7 +1084,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1075
1084
|
headers: {
|
|
1076
1085
|
"Content-Type": "application/json",
|
|
1077
1086
|
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1078
|
-
"HTTP-Referer": "https://gajae
|
|
1087
|
+
"HTTP-Referer": "https://gaebal-gajae.dev/",
|
|
1079
1088
|
"X-OpenRouter-Title": "Gajae Code",
|
|
1080
1089
|
"X-OpenRouter-Categories": "cli-agent",
|
|
1081
1090
|
},
|