Compare commits

..
3 Commits
Author SHA1 Message Date
ztimson ee4147e24e Fixed opanai early termination from tool calls
Publish Library / Build NPM Project (push) Successful in 46s
Publish Library / Tag Version (push) Successful in 15s
2026-09-18 16:07:58 -04:00
ztimson d29c0ca389 Fixed opanai early termination from tool calls
Publish Library / Build NPM Project (push) Successful in 55s
Publish Library / Tag Version (push) Successful in 9s
2026-09-18 02:15:02 -04:00
ztimson 4203cb34ef Better fact organization
Publish Library / Build NPM Project (push) Successful in 40s
Publish Library / Tag Version (push) Successful in 10s
2026-09-14 12:22:49 -04:00
3 changed files with 34 additions and 13 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ztimson/ai-utils",
"version": "1.6.7",
"version": "1.6.10",
"description": "AI Utility library",
"author": "Zak Timson",
"license": "MIT",
+17 -9
View File
@@ -365,15 +365,23 @@ ${m.content}
- NEVER extract greetings, pleasantries, or anything the assistant itself said
- Extract the final/end state, not deltas
Path assignment rules:
- Reuse existing node names whenever possible, including when the subject is an alias/nickname of an existing node (e.g. "Rob" referring to an existing "People/Robert")
- Documents should be grouped and named by the root subject
- Person → People/Name
- Project → Projects/Name
- Concept → Concepts/Name
- A bug report, its investigation, should be nested and attached to the same root subject node
- Tickets/one-off tasks → file under the project/name/component they belong to
- Only create a new top-level node when the fact belongs to a genuinely new subject (person/project/concept)\`
Path assignment (entity) rules:
- Use the owning entity of the fact (even if implied): "New bug on project 51 -> Projects/51"
- When multiple facts relate to the same entity, pick a primary owner and wikilink related entities
- Reuse existing entities when the owner already has a node
- Always group under consistent entity roots (always plural):
- Projects/[Name] for all initiatives
- People/[Name] for all individuals
- History/[Name] for all historical figures/events
- Science/[Name] for all scientific concepts
- Child entities nest under their parent entity:
- Projects/51/Memory System, Projects/51/Bug-XYZ, not Bugs/51
- Science/AI/Model-X, not Model-X/AI
Wikilink rules:
- Use [[WikiLinks]] to connect related entities (e.g., [[Projects/51]], [[People/Robert]])
- Only link specific, existing or implied entity paths — skip generic terms
- Don't over-link: each link should add clarity or context, not noise
Available nodes:
${this.listNodes(store.list).map(n => `- ${n.name}: ${n.description}`).join('\n') || 'None yet.'}
+16 -3
View File
@@ -98,18 +98,21 @@ export class OpenAi extends LLMProvider {
throw err;
});
let usage: any, msg: any = {content: '', tool_calls: []};
let usage: any, finishReason: string | undefined, msg: any = {content: '', tool_calls: []};
if(options.stream) {
for await (const chunk of resp) {
if(controller.signal.aborted) break;
if(chunk.usage) usage = chunk.usage;
if(chunk.choices[0]?.finish_reason) finishReason = chunk.choices[0].finish_reason;
if(chunk.choices[0]?.delta?.content) {
msg.content += chunk.choices[0].delta.content;
options.stream({text: chunk.choices[0].delta.content});
}
if(chunk.choices[0]?.delta?.tool_calls) {
for(const deltaTC of chunk.choices[0].delta.tool_calls) {
const existing = msg.tool_calls.find((tc: any) => tc.index === deltaTC.index);
const existing = deltaTC.index != null
? msg.tool_calls.find((tc: any) => tc.index === deltaTC.index)
: (deltaTC.id ? msg.tool_calls.find((tc: any) => tc.id === deltaTC.id) : undefined);
if(existing) {
if(deltaTC.id) existing.id = deltaTC.id;
if(deltaTC.function?.name) existing.function.name = deltaTC.function.name;
@@ -126,11 +129,21 @@ export class OpenAi extends LLMProvider {
}
} else {
usage = resp.usage;
finishReason = resp.choices[0].finish_reason;
msg = resp.choices[0].message;
}
const duration = Date.now() - callStart;
const tps = usage?.completion_tokens && duration > 0 ? usage.completion_tokens / (duration / 1000) : 0;
if(finishReason === 'length' && !controller.signal.aborted) {
if(msg.content?.trim()) history.push({role: 'assistant', content: msg.content.trim(), timestamp: Date.now(), duration, tps});
throw new Error(`[OpenAI] Response hit token limit before completing`);
}
if(!finishReason && !controller.signal.aborted) {
throw new Error('[OpenAI] Stream ended prematurely - connection likely dropped');
}
const toolCalls = msg.tool_calls || [];
if(toolCalls.length && !controller.signal.aborted) {
if(msg.content?.trim()) history.push({role: 'assistant', content: msg.content.trim(), timestamp: Date.now(), duration, tps});
@@ -147,7 +160,7 @@ export class OpenAi extends LLMProvider {
if(!tool) { entry.error = 'Tool not found'; return; }
try {
const toolStream = options.stream && ((chunk: any) => {
if(chunk.done) { terminal = true; return; }
if(chunk.done) return;
options.stream!(chunk);
});
const result = await tool.fn(entry.args, toolStream, this.ai, tc.id);