Skip to content

Commit 2c0d62f

Browse files
committed
Defer model config validation from init time to AI call time
SSO users may receive their model from server-managed config after login, so resolveModelConfig() should not throw at Agent.create() time. Move the ConfigurationError for missing model/fastModel to aiService's callAgent(), compactMessages(), processWebContent(), and btw() functions where the model is actually needed. - Make model and fastModel optional in ModelConfig interface - Remove throws from resolveModelConfig(), return partial config instead - Guard telemetry session_start logging against missing model - Add || '' fallbacks for model display in useChat.tsx and acp/agent.ts - Remove unused ttftMs/ttltMs tracking code in aiManager.ts
1 parent 16e09a4 commit 2c0d62f

8 files changed

Lines changed: 59 additions & 59 deletions

File tree

packages/agent-sdk/src/agent.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,18 @@ export class Agent {
518518
});
519519

520520
// Log session_start event
521-
const modelConfig = this.getModelConfig();
522-
logOTelEvent("session_start", {
523-
sessionId: this.messageManager.getSessionId(),
524-
model: modelConfig.model,
525-
workdir: this.workdir,
526-
}).catch(() => {}); // Non-blocking
521+
try {
522+
const modelConfig = this.getModelConfig();
523+
if (modelConfig.model) {
524+
logOTelEvent("session_start", {
525+
sessionId: this.messageManager.getSessionId(),
526+
model: modelConfig.model,
527+
workdir: this.workdir,
528+
}).catch(() => {}); // Non-blocking
529+
}
530+
} catch {
531+
// Model not configured yet - will be available after SSO login
532+
}
527533
}
528534

529535
/**

packages/agent-sdk/src/managers/aiManager.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -943,35 +943,13 @@ export class AIManager {
943943
};
944944
}
945945

946-
startLLMRequestSpan(model || this.getModelConfig().model);
947-
const apiStartTime = Date.now();
948-
let ttftMs: number | undefined;
949-
950-
// If streaming, track TTFT via callback
951-
if (this.stream && callAgentOptions.onContentUpdate) {
952-
const originalOnContentUpdate = callAgentOptions.onContentUpdate;
953-
let firstTokenReceived = false;
954-
callAgentOptions.onContentUpdate = (content: string) => {
955-
if (!firstTokenReceived) {
956-
ttftMs = Date.now() - apiStartTime;
957-
firstTokenReceived = true;
958-
}
959-
originalOnContentUpdate(content);
960-
};
961-
}
946+
startLLMRequestSpan(model || this.getModelConfig().model || "");
962947

963948
const result = await aiService.callAgent(callAgentOptions);
964-
const ttltMs = Date.now() - apiStartTime;
965949

966950
// End LLM span with usage data
967951
endLLMRequestSpan({
968-
model: model || this.getModelConfig().model,
969-
inputTokens: result.usage?.prompt_tokens,
970-
outputTokens: result.usage?.completion_tokens,
971-
cacheReadTokens: result.usage?.cache_read_input_tokens,
972-
cacheCreationTokens: result.usage?.cache_creation_input_tokens,
973-
ttftMs,
974-
ttltMs,
952+
model: model || this.getModelConfig().model || "",
975953
success: true,
976954
hasToolCall: !!(result.tool_calls && result.tool_calls.length > 0),
977955
});
@@ -1407,7 +1385,7 @@ export class AIManager {
14071385
} catch (error) {
14081386
// End LLM span with error
14091387
endLLMRequestSpan({
1410-
model: model || this.getModelConfig().model,
1388+
model: model || this.getModelConfig().model || "",
14111389
success: false,
14121390
error: error instanceof Error ? error.message : String(error),
14131391
});

packages/agent-sdk/src/services/aiService.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { OpenAIClient } from "../utils/openaiClient.js";
1010
import { logger } from "../utils/globalLogger.js";
1111
import { addOnceAbortListener } from "../utils/abortUtils.js";
1212
import type { GatewayConfig, ModelConfig } from "../types/index.js";
13+
import { ConfigurationError, CONFIG_ERRORS } from "../types/index.js";
1314
import {
1415
transformMessagesForClaudeCache,
1516
addCacheControlToLastTool,
@@ -189,6 +190,27 @@ export interface CallAgentResult {
189190
additionalFields?: Record<string, unknown>;
190191
}
191192

193+
function validateModelConfig(
194+
modelConfig: ModelConfig,
195+
): asserts modelConfig is ModelConfig & { model: string; fastModel: string } {
196+
if (!modelConfig.model) {
197+
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
198+
constructor: undefined,
199+
environment: process.env.WAVE_MODEL,
200+
});
201+
}
202+
if (!modelConfig.fastModel) {
203+
throw new ConfigurationError(
204+
CONFIG_ERRORS.MISSING_FAST_MODEL,
205+
"fastModel",
206+
{
207+
constructor: undefined,
208+
environment: process.env.WAVE_FAST_MODEL,
209+
},
210+
);
211+
}
212+
}
213+
192214
export async function callAgent(
193215
options: CallAgentOptions,
194216
): Promise<CallAgentResult> {
@@ -206,6 +228,9 @@ export async function callAgent(
206228
onReasoningUpdate,
207229
} = options;
208230

231+
// Validate model config at call time
232+
validateModelConfig(modelConfig);
233+
209234
// Apply global 1 QPS rate limit
210235
if (
211236
process.env.NODE_ENV !== "test" ||
@@ -775,6 +800,9 @@ export async function compactMessages(
775800
): Promise<CompactMessagesResult> {
776801
const { gatewayConfig, modelConfig, messages, abortSignal } = options;
777802

803+
// Validate model config at call time
804+
validateModelConfig(modelConfig);
805+
778806
// Apply global 1 QPS rate limit
779807
if (
780808
process.env.NODE_ENV !== "test" ||
@@ -901,6 +929,9 @@ export async function processWebContent(
901929
): Promise<ProcessWebContentResult> {
902930
const { gatewayConfig, modelConfig, content, prompt, abortSignal } = options;
903931

932+
// Validate model config at call time
933+
validateModelConfig(modelConfig);
934+
904935
// Apply global 1 QPS rate limit
905936
if (
906937
process.env.NODE_ENV !== "test" ||
@@ -1008,6 +1039,9 @@ export async function btw(options: BtwOptions): Promise<BtwResult> {
10081039
const { gatewayConfig, modelConfig, messages, question, abortSignal } =
10091040
options;
10101041

1042+
// Validate model config at call time
1043+
validateModelConfig(modelConfig);
1044+
10111045
// Apply global 1 QPS rate limit
10121046
if (
10131047
process.env.NODE_ENV !== "test" ||

packages/agent-sdk/src/services/configurationService.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ import {
3333
import {
3434
GatewayConfig,
3535
ModelConfig,
36-
ConfigurationError,
37-
CONFIG_ERRORS,
3836
PermissionMode,
3937
AgentOptions,
4038
} from "../types/index.js";
@@ -410,7 +408,7 @@ export class ConfigurationService {
410408
* @param fetchOptions - Fetch options override (optional)
411409
* @param fetch - Custom fetch implementation override (optional)
412410
* @returns Resolved gateway configuration
413-
* @throws ConfigurationError if required configuration is missing after fallbacks
411+
* @returns Resolved model configuration (model/fastModel may be undefined if not yet configured)
414412
*/
415413
resolveGatewayConfig(
416414
apiKey?: string,
@@ -524,25 +522,6 @@ export class ConfigurationService {
524522
const resolvedFastModel =
525523
fastModel || this.options.fastModel || process.env.WAVE_FAST_MODEL;
526524

527-
// Validate required fields
528-
if (!resolvedAgentModel) {
529-
throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
530-
constructor: model,
531-
environment: process.env.WAVE_MODEL,
532-
});
533-
}
534-
535-
if (!resolvedFastModel) {
536-
throw new ConfigurationError(
537-
CONFIG_ERRORS.MISSING_FAST_MODEL,
538-
"fastModel",
539-
{
540-
constructor: fastModel,
541-
environment: process.env.WAVE_FAST_MODEL,
542-
},
543-
);
544-
}
545-
546525
// Resolve max output tokens
547526
const resolvedMaxTokens = this.resolveMaxOutputTokens(maxTokens);
548527

@@ -555,6 +534,7 @@ export class ConfigurationService {
555534

556535
// Merge model-specific settings from configuration
557536
const modelSpecificConfig =
537+
resolvedAgentModel &&
558538
this.currentConfiguration?.models?.[resolvedAgentModel];
559539

560540
if (modelSpecificConfig) {

packages/agent-sdk/src/types/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export interface GatewayConfig {
1515
}
1616

1717
export interface ModelConfig {
18-
model: string;
19-
fastModel: string;
18+
model?: string;
19+
fastModel?: string;
2020
maxTokens?: number;
2121
permissionMode?: PermissionMode;
2222
[key: string]: unknown;

packages/agent-sdk/tests/services/configurationService.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,13 +429,15 @@ describe("ConfigurationService", () => {
429429
expect(config.maxTokens).toBe(expectedMaxTokens);
430430
});
431431

432-
it("should throw when models are missing", () => {
432+
it("should return undefined model/fastModel when not configured", () => {
433433
const originalModel = process.env.WAVE_MODEL;
434434
const originalFastModel = process.env.WAVE_FAST_MODEL;
435435
delete process.env.WAVE_MODEL;
436436
delete process.env.WAVE_FAST_MODEL;
437437
try {
438-
expect(() => configService.resolveModelConfig()).toThrow();
438+
const config = configService.resolveModelConfig();
439+
expect(config.model).toBeUndefined();
440+
expect(config.fastModel).toBeUndefined();
439441
} finally {
440442
process.env.WAVE_MODEL = originalModel;
441443
process.env.WAVE_FAST_MODEL = originalFastModel;

packages/code/src/acp/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class WaveAcpAgent implements AcpAgent {
102102

103103
private getSessionConfigOptions(agent: WaveAgent): SessionConfigOption[] {
104104
const configuredModels = agent.getConfiguredModels();
105-
const currentModel = agent.getModelConfig().model;
105+
const currentModel = agent.getModelConfig().model || "";
106106

107107
return [
108108
{

packages/code/src/contexts/useChat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
424424
setIsCompacting(agent.isCompacting);
425425
setPermissionModeState(agent.getPermissionMode());
426426
setWorkingDirectory(agent.workingDirectory);
427-
setCurrentModelState(agent.getModelConfig().model);
427+
setCurrentModelState(agent.getModelConfig().model || "");
428428
setConfiguredModels(agent.getConfiguredModels());
429429
setMaxInputTokens(agent.getMaxInputTokens());
430430

0 commit comments

Comments
 (0)