fix(pi): inherit default model and api key for embedded agent
This commit is contained in:
@@ -100,6 +100,27 @@ describe('PiEmbeddedBackend', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('surfaces agent state error when no assistant text is produced', async () => {
|
||||
const mod = createModule(`
|
||||
export class Agent {
|
||||
constructor() {
|
||||
this.state = { messages: [], error: undefined };
|
||||
}
|
||||
async prompt() {
|
||||
this.state.error = "Missing API key for default provider";
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
try {
|
||||
const backend = new PiEmbeddedBackend({ module: mod.moduleUrl, timeoutMs: 2000 });
|
||||
await expect(backend.process({ prompt: 'hello', history: [] }))
|
||||
.rejects.toThrow('Missing API key for default provider');
|
||||
} finally {
|
||||
mod.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws when module cannot be loaded', async () => {
|
||||
const backend = new PiEmbeddedBackend({ module: '/definitely/missing/pi-module.mjs', timeoutMs: 2000 });
|
||||
await expect(backend.process({ prompt: 'hello', history: [] }))
|
||||
|
||||
@@ -33,6 +33,8 @@ interface PiModelFactoryModuleLike extends PiModuleLike {
|
||||
export interface PiEmbeddedBackendOptions {
|
||||
timeoutMs?: number;
|
||||
model?: string;
|
||||
defaultModelSpec?: string;
|
||||
getApiKey?: (provider: string) => string | undefined | Promise<string | undefined>;
|
||||
systemPromptMode?: PiSystemPromptMode;
|
||||
module?: string;
|
||||
}
|
||||
@@ -197,12 +199,16 @@ export class PiEmbeddedBackend implements ExternalBackend {
|
||||
readonly name = 'pi_embedded' as const;
|
||||
private readonly timeoutMs: number;
|
||||
private readonly model?: string;
|
||||
private readonly defaultModelSpec?: string;
|
||||
private readonly getApiKey?: (provider: string) => string | undefined | Promise<string | undefined>;
|
||||
private readonly systemPromptMode: PiSystemPromptMode;
|
||||
private readonly moduleOverride?: string;
|
||||
|
||||
constructor(options: PiEmbeddedBackendOptions = {}) {
|
||||
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
this.model = options.model;
|
||||
this.defaultModelSpec = options.defaultModelSpec;
|
||||
this.getApiKey = options.getApiKey;
|
||||
this.systemPromptMode = options.systemPromptMode ?? 'hybrid';
|
||||
this.moduleOverride = options.module;
|
||||
}
|
||||
@@ -294,14 +300,28 @@ export class PiEmbeddedBackend implements ExternalBackend {
|
||||
input: ExternalBackendRequest,
|
||||
moduleName: string,
|
||||
): Promise<string> {
|
||||
const agent = new AgentCtor();
|
||||
const modelSpec = this.model ?? this.defaultModelSpec;
|
||||
const model = modelSpec
|
||||
? await this.resolvePiModel(modelSpec, moduleName)
|
||||
: undefined;
|
||||
|
||||
const agentOptions: Record<string, unknown> = {};
|
||||
if (model) {
|
||||
agentOptions.initialState = { model };
|
||||
}
|
||||
if (this.getApiKey) {
|
||||
agentOptions.getApiKey = this.getApiKey;
|
||||
}
|
||||
|
||||
const agent = Object.keys(agentOptions).length > 0
|
||||
? new AgentCtor(agentOptions)
|
||||
: new AgentCtor();
|
||||
if (!agent || typeof agent !== 'object') {
|
||||
throw new Error('Pi Agent constructor returned an invalid object');
|
||||
}
|
||||
const agentObj = agent as PiSessionLike;
|
||||
|
||||
if (this.model) {
|
||||
const model = await this.resolvePiModel(this.model, moduleName);
|
||||
if (model) {
|
||||
const setModel = agentObj.setModel;
|
||||
if (typeof setModel === 'function') {
|
||||
await Promise.resolve(setModel.call(agent, model));
|
||||
@@ -348,6 +368,10 @@ export class PiEmbeddedBackend implements ExternalBackend {
|
||||
}
|
||||
}
|
||||
|
||||
const stateError = (state as PiSessionLike).error;
|
||||
if (typeof stateError === 'string' && stateError.trim().length > 0) {
|
||||
throw new Error(`Pi Agent runtime produced no assistant text: ${stateError}`);
|
||||
}
|
||||
throw new Error('Pi Agent runtime produced no assistant text');
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,39 @@ import {
|
||||
type ExternalBackendName,
|
||||
} from '../backends/index.js';
|
||||
|
||||
function collectModelApiKeys(config: Config): Record<string, string> {
|
||||
const keys: Record<string, string> = {};
|
||||
const candidates = [
|
||||
config.models.default,
|
||||
config.models.fast,
|
||||
config.models.complex,
|
||||
config.models.local,
|
||||
...Object.values(config.models.local_providers ?? {}),
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate?.provider || !candidate.api_key) {
|
||||
continue;
|
||||
}
|
||||
if (candidate.api_key.trim().length === 0) {
|
||||
continue;
|
||||
}
|
||||
keys[candidate.provider] = candidate.api_key;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
function defaultPiModelSpec(config: Config): string {
|
||||
return `${config.models.default.provider}:${config.models.default.model}`;
|
||||
}
|
||||
|
||||
export function createConfiguredExternalBackends(config: Config): {
|
||||
externalBackends: Partial<Record<ExternalBackendName, ExternalBackend>>;
|
||||
defaultName?: ExternalBackendName;
|
||||
} {
|
||||
const backends: Partial<Record<ExternalBackendName, ExternalBackend>> = {};
|
||||
const modelApiKeys = collectModelApiKeys(config);
|
||||
|
||||
if (config.backends.claude_code.enabled) {
|
||||
backends.claude_code = new ClaudeCodeBackend(
|
||||
@@ -82,6 +110,8 @@ export function createConfiguredExternalBackends(config: Config): {
|
||||
backends.pi_embedded = new PiEmbeddedBackend({
|
||||
timeoutMs: config.backends.pi_embedded.timeout_ms,
|
||||
model: config.backends.pi_embedded.model,
|
||||
defaultModelSpec: defaultPiModelSpec(config),
|
||||
getApiKey: (provider) => modelApiKeys[provider],
|
||||
systemPromptMode: config.backends.pi_embedded.system_prompt_mode,
|
||||
module: config.backends.pi_embedded.module,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user