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 () => {
|
it('throws when module cannot be loaded', async () => {
|
||||||
const backend = new PiEmbeddedBackend({ module: '/definitely/missing/pi-module.mjs', timeoutMs: 2000 });
|
const backend = new PiEmbeddedBackend({ module: '/definitely/missing/pi-module.mjs', timeoutMs: 2000 });
|
||||||
await expect(backend.process({ prompt: 'hello', history: [] }))
|
await expect(backend.process({ prompt: 'hello', history: [] }))
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ interface PiModelFactoryModuleLike extends PiModuleLike {
|
|||||||
export interface PiEmbeddedBackendOptions {
|
export interface PiEmbeddedBackendOptions {
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
model?: string;
|
model?: string;
|
||||||
|
defaultModelSpec?: string;
|
||||||
|
getApiKey?: (provider: string) => string | undefined | Promise<string | undefined>;
|
||||||
systemPromptMode?: PiSystemPromptMode;
|
systemPromptMode?: PiSystemPromptMode;
|
||||||
module?: string;
|
module?: string;
|
||||||
}
|
}
|
||||||
@@ -197,12 +199,16 @@ export class PiEmbeddedBackend implements ExternalBackend {
|
|||||||
readonly name = 'pi_embedded' as const;
|
readonly name = 'pi_embedded' as const;
|
||||||
private readonly timeoutMs: number;
|
private readonly timeoutMs: number;
|
||||||
private readonly model?: string;
|
private readonly model?: string;
|
||||||
|
private readonly defaultModelSpec?: string;
|
||||||
|
private readonly getApiKey?: (provider: string) => string | undefined | Promise<string | undefined>;
|
||||||
private readonly systemPromptMode: PiSystemPromptMode;
|
private readonly systemPromptMode: PiSystemPromptMode;
|
||||||
private readonly moduleOverride?: string;
|
private readonly moduleOverride?: string;
|
||||||
|
|
||||||
constructor(options: PiEmbeddedBackendOptions = {}) {
|
constructor(options: PiEmbeddedBackendOptions = {}) {
|
||||||
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||||
this.model = options.model;
|
this.model = options.model;
|
||||||
|
this.defaultModelSpec = options.defaultModelSpec;
|
||||||
|
this.getApiKey = options.getApiKey;
|
||||||
this.systemPromptMode = options.systemPromptMode ?? 'hybrid';
|
this.systemPromptMode = options.systemPromptMode ?? 'hybrid';
|
||||||
this.moduleOverride = options.module;
|
this.moduleOverride = options.module;
|
||||||
}
|
}
|
||||||
@@ -294,14 +300,28 @@ export class PiEmbeddedBackend implements ExternalBackend {
|
|||||||
input: ExternalBackendRequest,
|
input: ExternalBackendRequest,
|
||||||
moduleName: string,
|
moduleName: string,
|
||||||
): Promise<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') {
|
if (!agent || typeof agent !== 'object') {
|
||||||
throw new Error('Pi Agent constructor returned an invalid object');
|
throw new Error('Pi Agent constructor returned an invalid object');
|
||||||
}
|
}
|
||||||
const agentObj = agent as PiSessionLike;
|
const agentObj = agent as PiSessionLike;
|
||||||
|
|
||||||
if (this.model) {
|
if (model) {
|
||||||
const model = await this.resolvePiModel(this.model, moduleName);
|
|
||||||
const setModel = agentObj.setModel;
|
const setModel = agentObj.setModel;
|
||||||
if (typeof setModel === 'function') {
|
if (typeof setModel === 'function') {
|
||||||
await Promise.resolve(setModel.call(agent, model));
|
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');
|
throw new Error('Pi Agent runtime produced no assistant text');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,11 +44,39 @@ import {
|
|||||||
type ExternalBackendName,
|
type ExternalBackendName,
|
||||||
} from '../backends/index.js';
|
} 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): {
|
export function createConfiguredExternalBackends(config: Config): {
|
||||||
externalBackends: Partial<Record<ExternalBackendName, ExternalBackend>>;
|
externalBackends: Partial<Record<ExternalBackendName, ExternalBackend>>;
|
||||||
defaultName?: ExternalBackendName;
|
defaultName?: ExternalBackendName;
|
||||||
} {
|
} {
|
||||||
const backends: Partial<Record<ExternalBackendName, ExternalBackend>> = {};
|
const backends: Partial<Record<ExternalBackendName, ExternalBackend>> = {};
|
||||||
|
const modelApiKeys = collectModelApiKeys(config);
|
||||||
|
|
||||||
if (config.backends.claude_code.enabled) {
|
if (config.backends.claude_code.enabled) {
|
||||||
backends.claude_code = new ClaudeCodeBackend(
|
backends.claude_code = new ClaudeCodeBackend(
|
||||||
@@ -82,6 +110,8 @@ export function createConfiguredExternalBackends(config: Config): {
|
|||||||
backends.pi_embedded = new PiEmbeddedBackend({
|
backends.pi_embedded = new PiEmbeddedBackend({
|
||||||
timeoutMs: config.backends.pi_embedded.timeout_ms,
|
timeoutMs: config.backends.pi_embedded.timeout_ms,
|
||||||
model: config.backends.pi_embedded.model,
|
model: config.backends.pi_embedded.model,
|
||||||
|
defaultModelSpec: defaultPiModelSpec(config),
|
||||||
|
getApiKey: (provider) => modelApiKeys[provider],
|
||||||
systemPromptMode: config.backends.pi_embedded.system_prompt_mode,
|
systemPromptMode: config.backends.pi_embedded.system_prompt_mode,
|
||||||
module: config.backends.pi_embedded.module,
|
module: config.backends.pi_embedded.module,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user