Make credential requests durable and store proxy authentication

This commit is contained in:
2026-11-06 21:13:40 +00:00
parent 16a9a9eb6c
commit abe420f0d4
12 changed files with 21279 additions and 62 deletions
+102 -42
View File
@@ -33805,9 +33805,9 @@ async function createAgent(tx, conversation) {
function agentHooks(agent, taskName) {
const handlers = [];
for (const extension of agent.extensions) {
for (const hook of extension.hooks ?? []) {
if (hook.task === taskName)
handlers.push({ extension: extension.name, handlers: hook.handlers });
for (const hook2 of extension.hooks ?? []) {
if (hook2.task === taskName)
handlers.push({ extension: extension.name, handlers: hook2.handlers });
}
}
return handlers;
@@ -33815,7 +33815,7 @@ function agentHooks(agent, taskName) {
function sectionExtension(agent, key) {
let found;
for (const extension of agent.extensions) {
const provides = extension.sections?.some((section) => section.key === key) === true;
const provides = extension.sections?.some((section2) => section2.key === key) === true;
const wraps = extension.wraps?.some((wrap) => "section" in wrap && wrap.section === key) === true;
if (provides || wraps)
found = extension.name;
@@ -33840,8 +33840,8 @@ function resolveAgent(state2, snapshot, settings, report2) {
composed.set(tool.name, tool);
const sections = /* @__PURE__ */ new Map();
for (const extension of extensions) {
for (const section of extension.sections ?? [])
sections.set(section.key, section);
for (const section2 of extension.sections ?? [])
sections.set(section2.key, section2);
}
for (const extension of extensions) {
for (const wrap of extension.wraps ?? []) {
@@ -33849,7 +33849,7 @@ function resolveAgent(state2, snapshot, settings, report2) {
if ("tool" in wrap)
applyWrap(composed, wrap.tool, (tool) => wrap.wrap(tool), (tool) => tool.name, wrapReport);
else
applyWrap(sections, wrap.section, (section) => wrap.wrap(section), (section) => section.key, wrapReport);
applyWrap(sections, wrap.section, (section2) => wrap.wrap(section2), (section2) => section2.key, wrapReport);
}
}
const enabled = filterTools([...composed.values()], state2?.tools);
@@ -34685,24 +34685,24 @@ function replaySections(messages) {
}
async function renderSections(sections, input, shown, report2, context) {
const desired = /* @__PURE__ */ new Map();
for (const section of sections) {
for (const section2 of sections) {
let text2;
try {
text2 = await section.render(input, context);
text2 = await section2.render(input, context);
} catch (error) {
if (context.abortSignal?.aborted)
throw error;
report2(error, section);
const kept = shown.get(section.key);
report2(error, section2);
const kept = shown.get(section2.key);
if (kept !== void 0)
desired.set(section.key, kept);
desired.set(section2.key, kept);
continue;
}
if (text2 === void 0)
continue;
desired.set(section.key, section.tag === false ? text2 : `<${section.key}>
desired.set(section2.key, section2.tag === false ? text2 : `<${section2.key}>
${text2}
</${section.key}>`);
</${section2.key}>`);
}
return desired;
}
@@ -38706,11 +38706,11 @@ var ToolTask = defineTask({
return reject(runtime, task.input, call, tool, invalid(checked.error), context);
let args = checked.args;
let block;
await runtime.hooks.each("beforeTool", async (hook) => {
await runtime.hooks.each("beforeTool", async (hook2) => {
if (block !== void 0)
return;
try {
const decision = await hook({ ...call, arguments: args }, runtime, context);
const decision = await hook2({ ...call, arguments: args }, runtime, context);
if (decision?.block !== void 0)
block = decision.block;
else if (decision?.arguments !== void 0)
@@ -39052,8 +39052,8 @@ async function finalResult(runtime, input, call, tool, result, reported, context
details: result.details === void 0 ? reported.details : result.details,
diagnostics: [...reported.diagnostics, ...result.diagnostics ?? []]
};
await runtime.hooks.each("afterTool", async (hook) => {
final = await hook(call, final, runtime, context) ?? final;
await runtime.hooks.each("afterTool", async (hook2) => {
final = await hook2(call, final, runtime, context) ?? final;
});
if (retained !== void 0 && retained.droppedBytes > 0 && final.output === output) {
harness2.push(truncated(retained, reported.limits.retain));
@@ -39085,8 +39085,8 @@ async function finalResult(runtime, input, call, tool, result, reported, context
}
async function afterHarnessResult(runtime, input, call, tool, result, truncation, context) {
let final = result;
await runtime.hooks.each("afterTool", async (hook) => {
final = await hook(call, final, runtime, context) ?? final;
await runtime.hooks.each("afterTool", async (hook2) => {
final = await hook2(call, final, runtime, context) ?? final;
});
const harness2 = [];
if (truncation !== void 0 && final.output === result.output)
@@ -39406,9 +39406,9 @@ var GenerationTask = defineTask({
runtime.report(error, { kind: "host", callback: "env" });
}
const input = { conversationId, agent, env, shown: Object.fromEntries(shown), read: runtime };
const desired = await renderSections(agent.sections, input, shown, (error, section) => runtime.report(error, {
const desired = await renderSections(agent.sections, input, shown, (error, section2) => runtime.report(error, {
kind: "extension",
...optionalExtension(sectionExtension(agent, section.key))
...optionalExtension(sectionExtension(agent, section2.key))
}), context);
const entries2 = planSystemEntries(view2, desired, agent.tools, runtime.now());
const threshold = compacted === void 0 ? thresholdCompaction(view2, entries2, resolved.contextWindow, compactionPolicy(settings, model3)) : void 0;
@@ -39456,8 +39456,8 @@ var GenerationTask = defineTask({
return failNoModel(runtime, ref, context);
const view2 = await runtime.context(conversationId, context, { at: cutoff });
let messages = view2.messages;
await runtime.hooks.each("beforeRequest", async (hook) => {
const replaced = await hook({ messages }, runtime, context);
await runtime.hooks.each("beforeRequest", async (hook2) => {
const replaced = await hook2({ messages }, runtime, context);
if (replaced !== void 0)
messages = replaced.messages;
});
@@ -39545,8 +39545,8 @@ var GenerationTask = defineTask({
if (cutOffTooOften(task, runtime))
return endAnsweredCutOff(runtime, task, run2.answer, context);
const prepared = [];
await runtime.hooks.each("afterRun", async (hook) => {
const asked = await hook(run2, runtime, context);
await runtime.hooks.each("afterRun", async (hook2) => {
const asked = await hook2(run2, runtime, context);
if (asked !== void 0)
prepareRunEndTasks(runtime, asked.tasks, prepared);
});
@@ -39754,8 +39754,8 @@ async function convertPartial(tx, live, conversationId) {
}
async function providerPayload(runtime, model3, payload, context) {
let current = payload;
await runtime.hooks.each("beforeProviderRequest", async (hook) => {
const replaced = await hook({ model: model3, payload: current }, runtime, context);
await runtime.hooks.each("beforeProviderRequest", async (hook2) => {
const replaced = await hook2({ model: model3, payload: current }, runtime, context);
if (replaced !== void 0)
current = replaced.payload;
});
@@ -39839,7 +39839,7 @@ async function classify2(runtime, request2, message, context) {
}, context);
return;
}
await runtime.hooks.each("afterResponse", (hook) => hook(message, runtime, context));
await runtime.hooks.each("afterResponse", (hook2) => hook2(message, runtime, context));
const calls = message.content.filter((content) => content.type === "toolCall");
if (message.stopReason === "toolUse" && calls.length > 0) {
return startToolRound(runtime, request2, message, calls, context);
@@ -39892,10 +39892,10 @@ async function classify2(runtime, request2, message, context) {
}
async function answer(runtime, message, context) {
let continuation;
await runtime.hooks.each("onYield", async (hook) => {
await runtime.hooks.each("onYield", async (hook2) => {
if (continuation !== void 0)
return;
continuation = (await hook(message, runtime, context))?.continue;
continuation = (await hook2(message, runtime, context))?.continue;
});
const afterRun = await asksAfterRun(runtime, context);
const conversationId = runtime.conversationId;
@@ -40038,7 +40038,7 @@ async function finishToolRound(runtime, assistant, tools, context) {
});
const slots = (await runtime.snapshot(LiveDoc, conversationId, context))?.tools ?? [];
const results = slots.flatMap((slot) => slot.entry === void 0 ? [] : [slot.entry]);
await runtime.hooks.each("afterTools", (hook) => hook(assistant, results, runtime, context));
await runtime.hooks.each("afterTools", (hook2) => hook2(assistant, results, runtime, context));
const ordered = slots.map((slot) => slot.taskId === void 0 ? void 0 : controls.get(slot.taskId));
const terminate = slots.length > 0 && ordered.every((control) => control?.terminate === true);
const added = ordered.flatMap((control) => control?.addTools ?? []);
@@ -40583,9 +40583,9 @@ var CompactionTask = defineTask({
...instructions === void 0 ? {} : { instructions }
};
let decision;
await runtime.hooks.each("beforeCompact", async (hook) => {
await runtime.hooks.each("beforeCompact", async (hook2) => {
if (decision === void 0)
decision = await hook(compaction, runtime, context);
decision = await hook2(compaction, runtime, context);
});
if (decision !== void 0 && "decline" in decision)
return complete(runtime, context);
@@ -40919,7 +40919,7 @@ var RegistryState = class {
return this.#extensions.flatMap((extension) => (extension.tools ?? []).map((tool) => ({ extension, tool })));
}
sections() {
return this.#extensions.flatMap((extension) => (extension.sections ?? []).map((section) => ({ extension, section })));
return this.#extensions.flatMap((extension) => (extension.sections ?? []).map((section2) => ({ extension, section: section2 })));
}
tasks() {
return [...this.#tasks.values()];
@@ -44424,10 +44424,67 @@ function environment(cwd = "/", conversation) {
return env;
}
// main.ts
if (!process.env.PI_PROXY_BASE_URL || !process.env.PI_PROXY_API_KEY) {
throw new Error("PI_PROXY_BASE_URL and PI_PROXY_API_KEY are required");
// credentials.ts
var Requests = defineDoc({
kind: "sausage.credential.requests",
version: 1,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({ ids: [] }),
checkpointWhen: () => false
});
var Wait = defineTask({
name: "sausage.credential.wait",
version: 1,
initial: () => ({ phase: "wait" }),
phases: {
wait: async (task, runtime, context) => {
const request2 = Deno.core.ops.op_credential_requests().find((r) => r.id === task.input.id);
if (!request2 || request2.state === "pending") {
await runtime.sleepUntil(Date.now() + 1e3, { phase: "wait" }, context);
return;
}
const conversation = await runtime.conversation(runtime.conversationId, context);
await runtime.commit(async (tx) => {
await conversation.submit(tx, { type: "input", content: request2.message, requestId: `credential:${request2.id}` });
return { status: "terminal", outcome: { status: "completed", result: null } };
}, context);
}
},
abort: (_task, runtime, context) => runtime.commit(() => ({ status: "terminal", outcome: { status: "aborted" } }), context)
});
var CredentialRequests = defineExtension({ name: "sausage.credentials", tasks: [Wait] });
async function discoverRequests(harness2, context) {
for (const request2 of Deno.core.ops.op_credential_requests()) {
const conversation = await harness2.conversation(Number(request2.conversation), context);
if (!conversation) continue;
await conversation.commit(async (tx) => {
const recorded = await tx.doc(Requests, conversation.id);
if (recorded.ids.includes(request2.id)) return;
await tx.createTask(Wait, { id: request2.id }, { ownership: { kind: "conversation" }, background: true });
recorded.ids.push(request2.id);
}, context);
}
}
async function startRequests(harness2, context) {
await discoverRequests(harness2, context);
let active = true;
const pending = (async () => {
while (active) {
await Deno.core.ops.op_credential_next();
if (active) await discoverRequests(harness2, context);
}
})();
return async () => {
active = false;
await pending;
};
}
// main.ts
var proxy = Deno.core.ops.op_model_proxy();
if (!proxy?.base_url || !proxy?.api_key) throw new Error("The llm-proxy credential is required");
var models = createModels();
var provider2 = openaiProvider();
var model2 = {
@@ -44435,7 +44492,7 @@ var model2 = {
name: "gpt-6.1-sol",
provider: "openai",
api: "openai-responses",
baseUrl: `${process.env.PI_PROXY_BASE_URL}/v1`,
baseUrl: `${proxy.base_url}/v1`,
reasoning: true,
input: ["text"],
thinkingLevelMap: { off: null, minimal: null },
@@ -44449,21 +44506,22 @@ models.setProvider({
getModels: () => [model2],
auth: { apiKey: { name: "Sausage proxy", resolve: async () => ({
auth: {
apiKey: process.env.PI_PROXY_API_KEY,
apiKey: proxy.api_key,
baseUrl: model2.baseUrl,
headers: { "Shopify-Usage-Tag": JSON.stringify(["pi", "sausage"]) }
},
source: "PI_PROXY_API_KEY"
source: "llm-proxy"
}) } }
});
var registry = createRegistry();
registry.install(CodingTools);
registry.install(CredentialRequests);
var harness = await Harness.open(await openStorage(globalThis.storagePath), {
models,
registry,
env: (target) => environment(target.cwd ?? "/", target.conversationId)
}, BACKGROUND_CONTEXT);
var root = await harness.root(BACKGROUND_CONTEXT, { agent: { model: { provider: "openai", modelId: model2.id }, thinkingLevel: "low", cwd: "/", extensions: [CodingTools] } });
var root = await harness.root(BACKGROUND_CONTEXT, { agent: { model: { provider: "openai", modelId: model2.id }, thinkingLevel: "low", cwd: "/", extensions: [CodingTools, CredentialRequests] } });
var watch = await root.watch(BACKGROUND_CONTEXT);
var emitted = /* @__PURE__ */ new Map();
var partial = "";
@@ -44488,6 +44546,7 @@ for (const entry2 of watch.value.entries) {
watch.start((event) => {
if (event.kind === "value") render(event.value);
});
var stopRequests = await startRequests(harness, BACKGROUND_CONTEXT);
harness.resume();
await harness.waitForIdle(BACKGROUND_CONTEXT);
while (true) {
@@ -44503,5 +44562,6 @@ while (true) {
Deno.core.ops.op_output("\n");
if (result.status !== "done") throw new Error(`Run ended: ${JSON.stringify(result)}`);
}
await stopRequests();
await watch.stop();
await harness.close(BACKGROUND_CONTEXT);