This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { z } from 'zod';
|
||||
import { PrimeOpenAIChatProvider, type PiggyChatEvent } from '../src/chat';
|
||||
import { defineTool } from '../src/provider';
|
||||
|
||||
async function collect(stream: AsyncIterable<PiggyChatEvent>): Promise<PiggyChatEvent[]> {
|
||||
const events: PiggyChatEvent[] = [];
|
||||
for await (const event of stream) events.push(event);
|
||||
return events;
|
||||
}
|
||||
|
||||
function eventStream(events: unknown[]): Response {
|
||||
const text = events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join('') + 'data: [DONE]\n\n';
|
||||
const midpoint = Math.floor(text.length / 2);
|
||||
const encoder = new TextEncoder();
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(text.slice(0, midpoint)));
|
||||
controller.enqueue(encoder.encode(text.slice(midpoint)));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{ headers: { 'content-type': 'text/event-stream' } },
|
||||
);
|
||||
}
|
||||
|
||||
test('interactive streaming keeps reasoning, tools and final content as separate events', async () => {
|
||||
const bodies: Record<string, unknown>[] = [];
|
||||
let call = 0;
|
||||
const fetchImpl: typeof fetch = async (_input, init) => {
|
||||
bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
||||
call += 1;
|
||||
return call === 1
|
||||
? eventStream([
|
||||
{
|
||||
choices: [{
|
||||
delta: {
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
id: 'call_1',
|
||||
function: { name: 'pig_get_', arguments: '{"id":' },
|
||||
}],
|
||||
},
|
||||
finish_reason: null,
|
||||
}],
|
||||
},
|
||||
{
|
||||
choices: [{
|
||||
delta: {
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
function: { name: 'record', arguments: '"record-1"}' },
|
||||
}],
|
||||
},
|
||||
finish_reason: 'tool_calls',
|
||||
}],
|
||||
},
|
||||
])
|
||||
: eventStream([
|
||||
{
|
||||
choices: [{ delta: { reasoning_content: 'Checked the scoped record.' }, finish_reason: null }],
|
||||
},
|
||||
{
|
||||
choices: [{ delta: { content: 'The commitment expires in October.' }, finish_reason: 'stop' }],
|
||||
},
|
||||
{ choices: [], usage: { prompt_tokens: 12, completion_tokens: 7 } },
|
||||
]);
|
||||
};
|
||||
|
||||
const provider = new PrimeOpenAIChatProvider({ apiKey: 'test', fetchImpl });
|
||||
const events = await collect(
|
||||
provider.run({
|
||||
message: 'When does this expire?',
|
||||
context: { type: 'contract', id: 'record-1' },
|
||||
tools: [
|
||||
defineTool({
|
||||
name: 'pig_get_record',
|
||||
description: 'Read the record in focus.',
|
||||
inputSchema: z.object({ id: z.string() }),
|
||||
execute: async ({ id }) => ({ id, expiresAt: '2026-10-01T00:00:00.000Z' }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
assert.deepEqual(events.map((event) => event.type), [
|
||||
'meta',
|
||||
'tool_call',
|
||||
'tool_result',
|
||||
'reasoning_delta',
|
||||
'content_delta',
|
||||
'done',
|
||||
]);
|
||||
assert.deepEqual(events[1], {
|
||||
type: 'tool_call',
|
||||
id: 'call_1',
|
||||
name: 'pig_get_record',
|
||||
arguments: { id: 'record-1' },
|
||||
});
|
||||
assert.equal(bodies.length, 2);
|
||||
for (const body of bodies) {
|
||||
assert.equal(body.reasoning_effort, 'none');
|
||||
assert.equal(body.stream, true);
|
||||
assert.equal(body.parallel_tool_calls, false);
|
||||
const advertisedTools = body.tools as { function: { name: string; description: string } }[];
|
||||
assert.deepEqual(
|
||||
advertisedTools.map((tool) => tool.function.name),
|
||||
['pig_get_record'],
|
||||
);
|
||||
assert.ok(!JSON.stringify(advertisedTools).match(/bash|filesystem|file_read|file_write/i));
|
||||
}
|
||||
const firstMessages = bodies[0]?.messages as { role: string; content: string }[];
|
||||
const systemPrompt = firstMessages?.find((message) => message.role === 'system')?.content;
|
||||
assert.match(systemPrompt ?? '', /no shell, filesystem, browser, code execution, or hidden tools/i);
|
||||
});
|
||||
|
||||
test('ambient coding tools are rejected before inference', async () => {
|
||||
let fetched = false;
|
||||
const provider = new PrimeOpenAIChatProvider({
|
||||
apiKey: 'test',
|
||||
fetchImpl: async () => {
|
||||
fetched = true;
|
||||
return eventStream([]);
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
collect(
|
||||
provider.run({
|
||||
message: 'List files',
|
||||
tools: [
|
||||
defineTool({
|
||||
name: 'bash',
|
||||
description: 'Run a command.',
|
||||
inputSchema: z.object({ command: z.string() }),
|
||||
execute: async () => null,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
),
|
||||
/outside the PIG tool boundary/,
|
||||
);
|
||||
assert.equal(fetched, false);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { AgentTask } from '@pig/db';
|
||||
import { z } from 'zod';
|
||||
import { defineTool, PrimeOpenAIProvider } from '../src/provider';
|
||||
|
||||
const task = {
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
kind: 'enrich_account',
|
||||
subject: '20000000-0000-4000-8000-000000000002',
|
||||
reason: 'Extract the cited description.',
|
||||
payload: { sourceUrl: 'https://example.com/source' },
|
||||
priority: 0,
|
||||
budget: 2,
|
||||
attempts: 1,
|
||||
maxAttempts: 3,
|
||||
dueAt: new Date(),
|
||||
leasedUntil: new Date(),
|
||||
leasedBy: 'test',
|
||||
startedAt: new Date(),
|
||||
finishedAt: null,
|
||||
outcome: null,
|
||||
error: null,
|
||||
requestedByUserId: null,
|
||||
createdAt: new Date(),
|
||||
} satisfies AgentTask;
|
||||
|
||||
test('Prime requests disable Nemotron reasoning and expose only supplied PIG tools', async () => {
|
||||
const bodies: Record<string, unknown>[] = [];
|
||||
let calls = 0;
|
||||
const fetchImpl: typeof fetch = async (_input, init) => {
|
||||
bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
||||
calls += 1;
|
||||
return Response.json(
|
||||
calls === 1
|
||||
? {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: null,
|
||||
tool_calls: [
|
||||
{
|
||||
id: 'call_1',
|
||||
type: 'function',
|
||||
function: { name: 'pig_read', arguments: '{}' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
||||
}
|
||||
: {
|
||||
choices: [{ message: { content: 'The cited record was inspected.' } }],
|
||||
usage: { prompt_tokens: 15, completion_tokens: 6 },
|
||||
},
|
||||
);
|
||||
};
|
||||
const provider = new PrimeOpenAIProvider({ apiKey: 'test', fetchImpl });
|
||||
|
||||
const result = await provider.run({
|
||||
task,
|
||||
tools: [
|
||||
defineTool({
|
||||
name: 'pig_read',
|
||||
description: 'Read application data.',
|
||||
inputSchema: z.object({}).strict(),
|
||||
execute: async () => ({ name: 'Example' }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(result.summary, 'The cited record was inspected.');
|
||||
assert.equal(result.inputTokens, 25);
|
||||
assert.equal(result.outputTokens, 11);
|
||||
assert.equal(bodies.length, 2);
|
||||
for (const body of bodies) {
|
||||
assert.equal(body.model, 'nvidia/nemotron-3-nano-30b-a3b');
|
||||
assert.equal(body.reasoning_effort, 'none');
|
||||
assert.equal(body.parallel_tool_calls, false);
|
||||
const tools = body.tools as { function: { name: string } }[];
|
||||
assert.deepEqual(tools.map((tool) => tool.function.name), ['pig_read']);
|
||||
assert.ok(!JSON.stringify(tools).includes('bash'));
|
||||
assert.ok(!JSON.stringify(tools).includes('filesystem'));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { retryBackoffMs } from '../src/queue';
|
||||
|
||||
test('task retry backoff grows but caps at one hour', () => {
|
||||
assert.equal(retryBackoffMs(1), 60_000);
|
||||
assert.equal(retryBackoffMs(2), 120_000);
|
||||
assert.equal(retryBackoffMs(20), 3_600_000);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { Database } from '@pig/db';
|
||||
import { createPigTools, PIG_TOOL_NAMES, recordFactInput } from '../src/tools';
|
||||
|
||||
test('the Piggy registry has no ambient coding tools', () => {
|
||||
const tools = createPigTools({} as Database, {
|
||||
task: {} as Parameters<typeof createPigTools>[1]['task'],
|
||||
agentRunId: '10000000-0000-4000-8000-000000000001',
|
||||
});
|
||||
assert.deepEqual(tools.map((tool) => tool.name), [...PIG_TOOL_NAMES]);
|
||||
assert.equal(tools.some((tool) => /bash|shell|file/i.test(tool.name)), false);
|
||||
});
|
||||
|
||||
test('agent claims require both a source URL and an evidence excerpt', () => {
|
||||
const claim = {
|
||||
targetType: 'account',
|
||||
targetId: '10000000-0000-4000-8000-000000000001',
|
||||
field: 'description',
|
||||
value: 'GPU cloud',
|
||||
score: 0.8,
|
||||
};
|
||||
assert.equal(recordFactInput.safeParse(claim).success, false);
|
||||
assert.equal(
|
||||
recordFactInput.safeParse({
|
||||
...claim,
|
||||
sourceUrl: 'https://example.com/source',
|
||||
evidenceExcerpt: 'Example operates a GPU cloud.',
|
||||
}).success,
|
||||
true,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user