@jskit-ai/assistant-core 0.1.199 → 0.1.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/conversation/README.md +8 -1
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/App.vue +4 -1
- package/package.json +99 -36
- package/src/client/conversation/AssistantConversationElement.vue +27 -4
- package/src/client/conversation/AssistantConversationStatus.vue +24 -0
- package/src/client/conversation/AssistantGoalControl.vue +8 -5
- package/src/client/conversation/AssistantTranscript.vue +10 -3
- package/src/client/conversation/index.js +2 -0
- package/src/client/conversation/messageDelivery.js +115 -32
- package/src/client/conversation/retainedConversation.js +32 -0
- package/src/client/conversation/useAssistantQuestions.js +76 -0
- package/src/client/lib/assistantApi.js +72 -2
- package/src/server/conversation/agentRun.js +222 -0
- package/src/server/conversation/attachments.js +93 -0
- package/src/server/conversation/claudeClient.js +221 -0
- package/src/server/conversation/claudeHistory.js +256 -0
- package/src/server/conversation/claudeProcess.js +327 -0
- package/src/server/conversation/claudeTools.js +42 -0
- package/src/server/conversation/claudeTurn.js +1082 -0
- package/src/server/conversation/codexConfiguration.js +1183 -0
- package/src/server/conversation/codexConversations.js +1082 -0
- package/src/server/conversation/codexDetachedTurn.js +332 -0
- package/src/server/conversation/codexEvents.js +194 -1
- package/src/server/conversation/codexGoalCommands.js +124 -0
- package/src/server/conversation/codexHelperExecution.js +306 -0
- package/src/server/conversation/codexHelperThreadLedger.js +804 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
- package/src/server/conversation/codexHelperThreads.js +638 -0
- package/src/server/conversation/codexHistoryAdapter.js +369 -0
- package/src/server/conversation/codexMessageCommands.js +663 -0
- package/src/server/conversation/codexMessageDelivery.js +211 -0
- package/src/server/conversation/codexNativeHistoryExport.js +95 -0
- package/src/server/conversation/codexNotificationObserver.js +482 -0
- package/src/server/conversation/codexNotificationQueue.js +103 -0
- package/src/server/conversation/codexObservationControl.js +174 -0
- package/src/server/conversation/codexOutput.js +931 -0
- package/src/server/conversation/codexProcess.js +1559 -0
- package/src/server/conversation/codexProcessLeader.js +14 -0
- package/src/server/conversation/codexProvider.js +2710 -0
- package/src/server/conversation/codexProviderOwner.js +1058 -0
- package/src/server/conversation/codexProviderSelection.js +218 -0
- package/src/server/conversation/codexReasoning.js +314 -0
- package/src/server/conversation/codexRenewalCommands.js +290 -0
- package/src/server/conversation/codexResultSettlement.js +409 -0
- package/src/server/conversation/codexRunJournal.js +658 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
- package/src/server/conversation/codexSessionCleanup.js +96 -0
- package/src/server/conversation/codexSessionReconciliation.js +284 -0
- package/src/server/conversation/codexThreadReadiness.js +397 -0
- package/src/server/conversation/codexThreadStatus.js +354 -0
- package/src/server/conversation/codexTurn.js +701 -255
- package/src/server/conversation/codexTurnControl.js +317 -0
- package/src/server/conversation/codexTurnRecovery.js +990 -0
- package/src/server/conversation/codexTurnState.js +549 -0
- package/src/server/conversation/commandHook.js +18 -0
- package/src/server/conversation/commandWrapper.js +37 -0
- package/src/server/conversation/configuration.js +43 -0
- package/src/server/conversation/continuity.js +289 -0
- package/src/server/conversation/fileStorage.js +55 -0
- package/src/server/conversation/hookBridge.js +19 -0
- package/src/server/conversation/index.js +13 -0
- package/src/server/conversation/localExecution.js +665 -0
- package/src/server/conversation/memoryStorage.js +7 -72
- package/src/server/conversation/nativeHistoryExport.js +90 -0
- package/src/server/conversation/nativeStorage.js +23 -0
- package/src/server/conversation/openCodeClient.js +209 -19
- package/src/server/conversation/openCodePlugin.js +84 -0
- package/src/server/conversation/openCodeProcess.js +476 -0
- package/src/server/conversation/openCodeRuntime.js +1578 -0
- package/src/server/conversation/openCodeTools.js +60 -0
- package/src/server/conversation/openCodeTurn.js +557 -0
- package/src/server/conversation/provider.js +30 -0
- package/src/server/conversation/providers/api.js +136 -0
- package/src/server/conversation/providers/claude.js +63 -0
- package/src/server/conversation/providers/claudeDriver.js +425 -0
- package/src/server/conversation/providers/codex.js +248 -0
- package/src/server/conversation/providers/codexDriver.js +1078 -0
- package/src/server/conversation/providers/opencode.js +49 -0
- package/src/server/conversation/providers/opencodeDriver.js +650 -0
- package/src/server/conversation/providers/scoped.js +312 -0
- package/src/server/conversation/runtime.js +2220 -0
- package/src/server/conversation/runtimeStateUpgrade.js +125 -0
- package/src/server/conversation/storage.js +184 -0
- package/src/server/conversation/streams.js +7 -2
- package/src/server/conversation/structuredOutput.js +240 -0
- package/src/server/conversation/systemPrompt.js +54 -0
- package/src/server/conversation/tools.js +84 -0
- package/src/server/conversation/transcript.js +5 -4
- package/src/server/hosts/claudeHistory.js +5 -0
- package/src/server/hosts/claudeProcess.js +11 -0
- package/src/server/hosts/claudeTurn.js +4 -0
- package/src/server/hosts/codexConfiguration.js +20 -0
- package/src/server/hosts/codexEvents.js +13 -0
- package/src/server/hosts/codexProcess.js +12 -0
- package/src/server/hosts/codexProvider.js +23 -0
- package/src/server/hosts/codexTurn.js +20 -0
- package/src/server/hosts/nativeHistory.js +3 -0
- package/src/server/hosts/openCodeClient.js +3 -0
- package/src/server/hosts/openCodeProcess.js +23 -0
- package/src/server/hosts/openCodeTurn.js +7 -0
- package/src/server/index.js +2 -0
- package/src/server/lib/aiConnectionClient.js +13 -6
- package/src/server/lib/assistantCompletion.js +190 -0
- package/src/server/lib/assistantToolLoop.js +314 -0
- package/src/server/lib/serviceToolCatalog.js +25 -10
- package/src/shared/assistantResponseText.js +29 -0
- package/src/shared/conversation/attachments.js +19 -0
- package/src/shared/conversation/index.js +3 -0
- package/src/shared/conversation/pagination.js +78 -0
- package/src/shared/conversation/patches.js +150 -0
- package/src/shared/conversation/questions.js +48 -0
- package/src/shared/conversation/streaming.js +7 -4
- package/src/shared/index.js +2 -0
- package/src/shared/nativeProviders.js +45 -0
- package/src/testing/conversationStorageContract.js +31 -1
- package/src/testing/nativeCodex.js +11 -0
- package/src/testing/nativeOpenCode.js +3 -0
- package/test/aiConnectionClient.test.js +2 -0
- package/test/apiConversationDriver.test.js +73 -0
- package/test/assistantApiSurfaceHeader.test.js +85 -0
- package/test/assistantBoundedToolLoop.test.js +285 -0
- package/test/assistantScroll.browser.test.js +16 -0
- package/test/claudeClient.test.js +189 -0
- package/test/claudeConversationSession.test.js +1173 -0
- package/test/claudeInstructionResume.test.js +82 -0
- package/test/claudeProcess.test.js +204 -0
- package/test/claudeTurn.test.js +253 -0
- package/test/codexConversationSession.test.js +2122 -0
- package/test/codexGoal.test.js +37 -0
- package/test/codexHelperDeletion.test.js +163 -0
- package/test/codexHelperThreadLedger.test.js +494 -0
- package/test/codexHistoryAdapter.test.js +541 -0
- package/test/codexInteractiveArguments.test.js +30 -0
- package/test/codexIsolation.test.js +756 -0
- package/test/codexModelCatalog.test.js +362 -0
- package/test/codexNativeHistoryExport.test.js +81 -0
- package/test/codexThreadSelection.test.js +136 -0
- package/test/codexTurn.test.js +2158 -0
- package/test/conversation.test.js +110 -0
- package/test/conversationAgentRun.test.js +326 -0
- package/test/conversationApiFacilities.test.js +90 -0
- package/test/conversationAttachments.test.js +238 -0
- package/test/conversationCommandWrapper.test.js +48 -0
- package/test/conversationConfiguration.test.js +174 -0
- package/test/conversationNativeStorage.test.js +25 -0
- package/test/conversationReplacement.test.js +172 -0
- package/test/conversationRuntime.test.js +241 -0
- package/test/conversationRuntimeStateUpgrade.test.js +144 -0
- package/test/conversationSession.test.js +1080 -0
- package/test/conversationStreaming.test.js +68 -0
- package/test/conversationSystemPrompt.test.js +84 -0
- package/test/conversationTools.test.js +416 -0
- package/test/fileConversationStorage.test.js +73 -0
- package/test/fixtures/codexHelperThreadLedger.js +39 -0
- package/test/localConversationExecution.test.js +90 -0
- package/test/messageDelivery.test.js +234 -1
- package/test/nativeConversationRetirement.test.js +215 -0
- package/test/openCodeClient.test.js +515 -0
- package/test/openCodeProcess.test.js +177 -0
- package/test/openCodeRuntime.test.js +322 -0
- package/test/openCodeTools.test.js +287 -0
- package/test/opencodeConversationSession.test.js +789 -0
- package/test/serviceToolCatalog.test.js +24 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
|
|
3
|
+
const unknownOutcome = "The application operation did not return a verified result. Inspect its target before requesting another execution.";
|
|
4
|
+
|
|
5
|
+
/** One turn's application calls. Native/API adapters only transport these calls. */
|
|
6
|
+
export function createConversationTools({ catalog, context, signal: turnSignal, previousCalls = [], authorize, save, emit,
|
|
7
|
+
maximumCalls = 32, discoveryOnly = false, transient = false, propagateErrors = false }) {
|
|
8
|
+
if (transient !== true && [authorize, save, emit].some(hook => typeof hook !== "function")) {
|
|
9
|
+
throw new TypeError("Conversation tools require authorization, durable save and event facilities.");
|
|
10
|
+
}
|
|
11
|
+
if (transient === true && previousCalls.length) {
|
|
12
|
+
throw new TypeError("Transient application tools cannot restore a previous execution.");
|
|
13
|
+
}
|
|
14
|
+
const toolSet = catalog.resolveToolSet(context, { discoveryOnly });
|
|
15
|
+
const calls = new Map(structuredClone(previousCalls).map(call => [call.id, call]));
|
|
16
|
+
let pending = Promise.resolve();
|
|
17
|
+
let requested = 0;
|
|
18
|
+
|
|
19
|
+
function execute(input, { signal = turnSignal } = {}) {
|
|
20
|
+
// Some native engines request tools concurrently. Keep application mutations
|
|
21
|
+
// ordered and recheck admission after each predecessor actually settles.
|
|
22
|
+
const combined = signal === turnSignal ? signal : AbortSignal.any([turnSignal, signal]);
|
|
23
|
+
const operation = pending.then(() => run(input, combined));
|
|
24
|
+
pending = operation.catch(() => {});
|
|
25
|
+
return operation;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function run({ id, name, arguments: argumentsText }, signal) {
|
|
29
|
+
await authorize?.();
|
|
30
|
+
signal.throwIfAborted();
|
|
31
|
+
if (typeof id !== "string" || !id || id.length > 256 || typeof name !== "string" || !name || name.length > 256 ||
|
|
32
|
+
typeof argumentsText !== "string") throw new TypeError("An application tool requires its call id, name and JSON arguments.");
|
|
33
|
+
if (Buffer.byteLength(argumentsText) > catalog.limits.maxToolArgumentBytes) throw new Error("The application tool arguments exceed their size limit.");
|
|
34
|
+
const input = { id, name, arguments: argumentsText };
|
|
35
|
+
if (++requested > maximumCalls) throw new Error("The assistant reached the application tool-call limit for this turn.");
|
|
36
|
+
const recorded = calls.get(id);
|
|
37
|
+
if (recorded) {
|
|
38
|
+
if (!isDeepStrictEqual(input, { id: recorded.id, name: recorded.name, arguments: recorded.arguments })) {
|
|
39
|
+
throw new Error("This application tool call id already belongs to different arguments.");
|
|
40
|
+
}
|
|
41
|
+
if (!recorded.result || recorded.status === "unknown") {
|
|
42
|
+
throw Object.assign(new Error(unknownOutcome), { code: "conversation_tool_outcome_unknown" });
|
|
43
|
+
}
|
|
44
|
+
return structuredClone(recorded.result);
|
|
45
|
+
}
|
|
46
|
+
const call = { ...input, status: "running", at: new Date().toISOString() };
|
|
47
|
+
calls.set(id, call);
|
|
48
|
+
// Canonical calls reserve durably before the effect. Explicit transient
|
|
49
|
+
// calls retain the same once-only state only for their non-resumable lifetime.
|
|
50
|
+
try { await save?.([...calls.values()]); }
|
|
51
|
+
catch (error) { calls.delete(id); throw error; }
|
|
52
|
+
await emit?.({ type: "tool", call: structuredClone(call) });
|
|
53
|
+
try {
|
|
54
|
+
await authorize?.();
|
|
55
|
+
signal.throwIfAborted();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
call.status = "not-executed";
|
|
58
|
+
call.result = { ok: false, error: { code: "conversation_tool_not_executed", message: "Execution stopped before the action began." } };
|
|
59
|
+
await save?.([...calls.values()]);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
let failed = false;
|
|
63
|
+
let failure;
|
|
64
|
+
call.result = await catalog.executeToolCall({ toolName: name, argumentsText, toolSet, context: { ...context, signal },
|
|
65
|
+
...(propagateErrors ? { onFailure(error) { failed = true; failure = error; } } : {}) });
|
|
66
|
+
call.status = !call.result.ok && call.result.error?.status >= 500 ? "unknown" : "complete";
|
|
67
|
+
// Keep the result in memory as well: retrySave can persist it after a failed
|
|
68
|
+
// write without executing the action again or repeating model inference.
|
|
69
|
+
await save?.([...calls.values()]);
|
|
70
|
+
await emit?.({ type: "tool", call: structuredClone(call) });
|
|
71
|
+
if (failed) throw failure;
|
|
72
|
+
if (!call.result.ok && call.result.error?.status >= 500) {
|
|
73
|
+
throw Object.assign(new Error(unknownOutcome), { code: "conversation_tool_outcome_unknown" });
|
|
74
|
+
}
|
|
75
|
+
return structuredClone(call.result);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
descriptors: toolSet.tools,
|
|
80
|
+
schemas: toolSet.tools.map(catalog.toOpenAiToolSchema), execute,
|
|
81
|
+
maximumArgumentBytes: catalog.limits.maxToolArgumentBytes,
|
|
82
|
+
records: () => structuredClone([...calls.values()])
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { normalizeText } from "./normalize.js";
|
|
2
2
|
|
|
3
3
|
/** Conversation policy, independent of database, filesystem, identity and HTTP. */
|
|
4
|
-
export function createConversationTranscript({ storage, clock = () => new Date() } = {}) {
|
|
4
|
+
export function createConversationTranscript({ storage, clock = () => new Date(), applicationTurns = false } = {}) {
|
|
5
5
|
if (typeof storage?.read !== "function" || typeof storage?.write !== "function") {
|
|
6
6
|
throw new TypeError("Conversation storage requires read(scope, callback) and write(scope, callback).");
|
|
7
7
|
}
|
|
@@ -44,15 +44,15 @@ export function createConversationTranscript({ storage, clock = () => new Date()
|
|
|
44
44
|
return ids.length ? transaction.readTurn(ids.at(-1)) : null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
async function append(scope, role, { text = "", messageId = "", at = "", attachments = [], turnMetadata = null, requireOpenTurn = false } = {}) {
|
|
47
|
+
async function append(scope, role, { text = "", messageId = "", at = "", attachments = [], turnMetadata = null, requireOpenTurn = false, outputId = "" } = {}) {
|
|
48
48
|
const messageText = normalizeText(text);
|
|
49
49
|
const id = normalizeText(messageId);
|
|
50
|
-
if (!messageText) return null;
|
|
50
|
+
if (!messageText && !(role === "user" && Array.isArray(attachments) && attachments.length)) return null;
|
|
51
51
|
return storage.write(scope, async (transaction) => {
|
|
52
52
|
if (id && await transaction.hasMessage(id)) return null;
|
|
53
53
|
const createdAt = new Date(at || clock());
|
|
54
54
|
const tail = ["assistant", "commentary", "thinking"].includes(role) ? await tailTurn(transaction) : null;
|
|
55
|
-
const open = tail?.user && !tail.assistant ? tail : null;
|
|
55
|
+
const open = (tail?.user || applicationTurns && tail?.system?.origin === "application") && !tail.assistant ? tail : null;
|
|
56
56
|
if (requireOpenTurn && !open) return null;
|
|
57
57
|
const thinkingOnly = role === "thinking" && !open && at && tail &&
|
|
58
58
|
!tail.system && !tail.user && !tail.assistant && !tail.commentary?.length &&
|
|
@@ -60,6 +60,7 @@ export function createConversationTranscript({ storage, clock = () => new Date()
|
|
|
60
60
|
const turnId = open?.turnId || (thinkingOnly ? tail.turnId : await transaction.nextTurnId());
|
|
61
61
|
await transaction.appendMessage(turnId, {
|
|
62
62
|
role, text: messageText, messageId: id, at: createdAt.toISOString(),
|
|
63
|
+
...(["assistant", "commentary", "thinking"].includes(role) && normalizeText(outputId) ? { outputId: normalizeText(outputId) } : {}),
|
|
63
64
|
...(role === "user" ? { attachments, turnMetadata } : {})
|
|
64
65
|
});
|
|
65
66
|
return transaction.readTurn(turnId);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
bindClaudeConversationAccount,
|
|
3
|
+
claudeCodeArguments,
|
|
4
|
+
claudeCatalogueModels,
|
|
5
|
+
claudeFlagSettings,
|
|
6
|
+
claudeModelConfiguration,
|
|
7
|
+
createClaudeAccountQueries,
|
|
8
|
+
createClaudeCodeProcess,
|
|
9
|
+
readClaudeCodeAuthStatus,
|
|
10
|
+
verifyClaudeProviderKey
|
|
11
|
+
} from "../conversation/claudeProcess.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
codexAppServerThreadSettings,
|
|
3
|
+
codexAppServerReadOnlyThreadSettings,
|
|
4
|
+
codexAppServerTurnSettings,
|
|
5
|
+
codexInteractiveArguments,
|
|
6
|
+
normalizeCodexThreadId,
|
|
7
|
+
codexAppServerProjectHookTrustConfig,
|
|
8
|
+
codexAuthOutputRequiresReconnect,
|
|
9
|
+
codexProviderConfiguration,
|
|
10
|
+
codexProviderFileConfiguration,
|
|
11
|
+
verifyCodexProviderKey,
|
|
12
|
+
codexProviderModelCatalog,
|
|
13
|
+
codexCatalogRows,
|
|
14
|
+
codexCatalogReasoningEfforts,
|
|
15
|
+
codexCatalogModels,
|
|
16
|
+
codexConfiguredModelCatalog,
|
|
17
|
+
createCodexAccountReader,
|
|
18
|
+
createCodexAppServerIsolation,
|
|
19
|
+
readCodexToolFreeConfiguration
|
|
20
|
+
} from "../conversation/codexConfiguration.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
codexContextUsageFromNotification,
|
|
3
|
+
codexAppServerContextRefreshReason,
|
|
4
|
+
codexAppServerNotificationEvent,
|
|
5
|
+
codexAppServerNotificationItemId,
|
|
6
|
+
codexAppServerNotificationTurnId,
|
|
7
|
+
codexAppServerProviderThreadAssistantSegments,
|
|
8
|
+
codexAppServerProviderThreadTurns,
|
|
9
|
+
codexAppServerProviderTurnForOperation,
|
|
10
|
+
codexAppServerProviderTurnId,
|
|
11
|
+
codexAppServerThreadError,
|
|
12
|
+
codexAppServerThreadTurnId
|
|
13
|
+
} from "../conversation/codexEvents.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CODEX_APP_SERVER_METADATA_SCHEMA_VERSION,
|
|
3
|
+
CODEX_APP_SERVER_RUNTIME_DIR_NAME,
|
|
4
|
+
CODEX_APP_SERVER_TRANSPORT,
|
|
5
|
+
codexAppServerExecutionMode,
|
|
6
|
+
codexAppServerHelperHomeDir,
|
|
7
|
+
codexAppServerHelperWorkspaceDir,
|
|
8
|
+
codexAppServerIsHelper,
|
|
9
|
+
createCodexAppServerRuntime,
|
|
10
|
+
normalizeCodexAppServerTerminalEnv,
|
|
11
|
+
runCodexAppServerProcess
|
|
12
|
+
} from "../conversation/codexProcess.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CODEX_APP_SERVER_INVALID_REQUEST_CODE,
|
|
3
|
+
CodexAppServerAgentProvider,
|
|
4
|
+
assertCodexAuthPreflightReady,
|
|
5
|
+
codexAppServerEndpointForTarget,
|
|
6
|
+
codexAppServerRequestIsInvalid,
|
|
7
|
+
codexAppServerRuntimeStopWasVerified,
|
|
8
|
+
codexCliResumeCommand,
|
|
9
|
+
codexRenewalThreadError,
|
|
10
|
+
codexTextInput,
|
|
11
|
+
codexTurnInput,
|
|
12
|
+
createCodexAppServerProviderOwner,
|
|
13
|
+
defineCodexRenewalThreadIds,
|
|
14
|
+
deleteCodexAppServerHelperThread,
|
|
15
|
+
deleteCodexAppServerThread,
|
|
16
|
+
ensureCodexAppServerThread,
|
|
17
|
+
inspectCodexAppServerMessageAdmission,
|
|
18
|
+
retireCodexConversationHistory,
|
|
19
|
+
resumeExactCodexAppServerThread,
|
|
20
|
+
sendCodexAppServerPrompt,
|
|
21
|
+
shellQuote,
|
|
22
|
+
startFreshCodexAppServerThread
|
|
23
|
+
} from "../conversation/codexProvider.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
codexAppServerAgentRun,
|
|
3
|
+
codexAppServerConversationTurnIsActive,
|
|
4
|
+
codexAppServerFrozenTurnInterruptResponse,
|
|
5
|
+
codexAppServerInterruptUnavailableResponse,
|
|
6
|
+
codexAppServerMessageDisplayText,
|
|
7
|
+
codexAppServerMessageText,
|
|
8
|
+
codexAppServerThreadStatus,
|
|
9
|
+
codexAppServerTurnState,
|
|
10
|
+
codexAppServerTurnStateFromAgentRun,
|
|
11
|
+
createCodexAppServerNotificationQueue,
|
|
12
|
+
createCodexAppServerRunOwner,
|
|
13
|
+
sendPreparedCodexAppServerHelperTurn
|
|
14
|
+
} from "../conversation/codexTurn.js";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
|
|
18
|
+
CODEX_HELPER_THREAD_LIFECYCLES,
|
|
19
|
+
createCodexHelperThreadLedgerOwner
|
|
20
|
+
} from "../conversation/codexHelperThreadLedger.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
OPENCODE_EXPECTED_VERSION,
|
|
3
|
+
OPENCODE_HOST,
|
|
4
|
+
OPENCODE_READY_TIMEOUT_MS,
|
|
5
|
+
assertOpenCodeModelProvider,
|
|
6
|
+
createOpenCodeConversationPlugin,
|
|
7
|
+
createOpenCodeServerProcess,
|
|
8
|
+
createOpenCodeSharedRuntime,
|
|
9
|
+
ensureOpenCodeSession,
|
|
10
|
+
limitOpenCodeModelOutput,
|
|
11
|
+
openCodeApplicationToolSchemas,
|
|
12
|
+
openCodeConversationAgents,
|
|
13
|
+
openCodeEnvironmentForDirectory,
|
|
14
|
+
openCodeEnvironmentForSession,
|
|
15
|
+
openCodeModel,
|
|
16
|
+
openCodeProcessEnvironment,
|
|
17
|
+
openCodeServerForDirectory,
|
|
18
|
+
readOpenCodeEnvironments,
|
|
19
|
+
readOpenCodeCatalog,
|
|
20
|
+
readOpenCodeZenModelIds,
|
|
21
|
+
verifyOpenCodeApiKey,
|
|
22
|
+
sameOpenCodeSelection
|
|
23
|
+
} from "../conversation/openCodeProcess.js";
|
package/src/server/index.js
CHANGED
|
@@ -14,3 +14,5 @@ export {
|
|
|
14
14
|
} from "./lib/ndjson.js";
|
|
15
15
|
export { resolveWorkspaceSlug } from "./lib/resolveWorkspaceSlug.js";
|
|
16
16
|
export { createServiceToolCatalog } from "./lib/serviceToolCatalog.js";
|
|
17
|
+
export { consumeCompletionStream, sanitizeAssistantMessageText } from "./lib/assistantCompletion.js";
|
|
18
|
+
export { runAssistantToolLoop, runBoundedAssistantToolLoop } from "./lib/assistantToolLoop.js";
|
|
@@ -47,7 +47,7 @@ function sdkMessages(messages) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** Accept only the server-side result of an authorized AI connection resolver. */
|
|
50
|
-
export function createAiConnectionClient(connection, { fetch, timeoutMs = 120_000, maxOutputTokens } = {}) {
|
|
50
|
+
export function createAiConnectionClient(connection, { fetch, timeoutMs = 120_000, maxOutputTokens, effort } = {}) {
|
|
51
51
|
if (!connection?.apiKey || !connection.model || !Object.hasOwn(providers, connection.sdkPackage)) {
|
|
52
52
|
throw new TypeError("An authorized AI connection with a supported SDK and model is required.");
|
|
53
53
|
}
|
|
@@ -61,7 +61,7 @@ export function createAiConnectionClient(connection, { fetch, timeoutMs = 120_00
|
|
|
61
61
|
tools: Object.fromEntries(tools.map(({ function: tool }) => [tool.name, {
|
|
62
62
|
description: tool.description, inputSchema: jsonSchema(tool.parameters)
|
|
63
63
|
}])),
|
|
64
|
-
abortSignal: signal, timeout: timeoutMs, maxRetries: 0, temperature, maxOutputTokens
|
|
64
|
+
abortSignal: signal, timeout: timeoutMs, maxRetries: 0, temperature, maxOutputTokens, reasoning: effort
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
return Object.freeze({
|
|
@@ -70,21 +70,28 @@ export function createAiConnectionClient(connection, { fetch, timeoutMs = 120_00
|
|
|
70
70
|
const result = await generateText(options(input));
|
|
71
71
|
return { choices: [{ message: { role: "assistant", content: result.text,
|
|
72
72
|
tool_calls: result.toolCalls.map(call => ({ id: call.toolCallId, type: "function",
|
|
73
|
-
function: { name: call.toolName, arguments: JSON.stringify(call.input) } })) } }] };
|
|
73
|
+
function: { name: call.toolName, arguments: JSON.stringify(call.input) } })) }, finish_reason: result.finishReason }] };
|
|
74
74
|
},
|
|
75
75
|
async *createChatCompletionStream(input) {
|
|
76
76
|
const result = streamText({ ...options(input), onError() {} });
|
|
77
77
|
let toolIndex = 0;
|
|
78
|
+
let failure;
|
|
78
79
|
for await (const part of result.fullStream) {
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
// Let the SDK settle its stream before reporting a rejected tool call.
|
|
81
|
+
// Cancelling this iterator mid-step leaves its completion unresolved.
|
|
82
|
+
if (part.type === "error" || (part.type === "tool-call" && part.invalid)) {
|
|
83
|
+
failure ||= part.error || new Error("The model returned an invalid tool call.");
|
|
84
|
+
}
|
|
85
|
+
if (part.type === "abort") failure ||= input?.signal?.reason || new Error("Assistant request was aborted.");
|
|
86
|
+
if (failure) continue;
|
|
81
87
|
if (part.type === "text-delta") yield { choices: [{ delta: { content: part.text } }] };
|
|
88
|
+
if (part.type === "finish") yield { choices: [{ delta: {}, finish_reason: part.finishReason }], usage: part.totalUsage };
|
|
82
89
|
if (part.type === "tool-call") {
|
|
83
|
-
if (part.invalid) throw part.error;
|
|
84
90
|
yield { choices: [{ delta: { tool_calls: [{ index: toolIndex++, id: part.toolCallId,
|
|
85
91
|
function: { name: part.toolName, arguments: JSON.stringify(part.input) } }] } }] };
|
|
86
92
|
}
|
|
87
93
|
}
|
|
94
|
+
if (failure) throw failure;
|
|
88
95
|
}
|
|
89
96
|
});
|
|
90
97
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
function extractTextDelta(deltaContent) {
|
|
4
|
+
if (typeof deltaContent === "string") {
|
|
5
|
+
return deltaContent;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!Array.isArray(deltaContent)) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return deltaContent
|
|
13
|
+
.map((entry) => {
|
|
14
|
+
if (typeof entry === "string") {
|
|
15
|
+
return entry;
|
|
16
|
+
}
|
|
17
|
+
if (!entry || typeof entry !== "object") {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
return String(entry.text || "");
|
|
21
|
+
})
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sanitizeAssistantMessageText(value, { preserveWhitespace = false } = {}) {
|
|
26
|
+
let source = String(value || "");
|
|
27
|
+
if (!source) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const blockPatterns = [
|
|
32
|
+
/<[^>\n]*function_calls[^>\n]*>[\s\S]*?(?:<\/[^>\n]*function_calls>|$)/gi,
|
|
33
|
+
/<[^>\n]*tool_calls?[^>\n]*>[\s\S]*?(?:<\/[^>\n]*tool_calls?[^>\n]*>|$)/gi,
|
|
34
|
+
/<[^>\n]*invoke\b[^>\n]*>[\s\S]*?(?:<\/[^>\n]*invoke>|$)/gi,
|
|
35
|
+
/<(?:analysis|reasoning|think)>[\s\S]*?(?:<\/(?:analysis|reasoning|think)>|$)/gi
|
|
36
|
+
];
|
|
37
|
+
for (const pattern of blockPatterns) {
|
|
38
|
+
source = source.replace(pattern, " ");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const inlineTagPatterns = [
|
|
42
|
+
/<[^>\n]*invoke\b[^>]*\/>/gi,
|
|
43
|
+
/<\/?[^>\n]*invoke[^>\n]*>/gi,
|
|
44
|
+
/<\/?[^>\n]*function_calls[^>\n]*>/gi,
|
|
45
|
+
/<\/?[^>\n]*tool_calls?[^>\n]*>/gi,
|
|
46
|
+
/<\/?[^>\n]*DSML[^>\n]*>/gi
|
|
47
|
+
];
|
|
48
|
+
for (const pattern of inlineTagPatterns) {
|
|
49
|
+
source = source.replace(pattern, " ");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (preserveWhitespace) return source;
|
|
53
|
+
|
|
54
|
+
return source
|
|
55
|
+
.split(/\r?\n/)
|
|
56
|
+
.map((line) => line.trim())
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseDsmlToolCallsFromText(value = "") {
|
|
62
|
+
const source = String(value || "");
|
|
63
|
+
if (!source) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const functionCallsMatch = source.match(
|
|
68
|
+
/<[^>\n]*function_calls[^>\n]*>([\s\S]*?)<\/[^>\n]*function_calls>/i
|
|
69
|
+
);
|
|
70
|
+
if (!functionCallsMatch) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const blockText = String(functionCallsMatch[1] || "");
|
|
75
|
+
if (!blockText) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const calls = [];
|
|
80
|
+
const invokePattern = /<[^>\n]*invoke\b([^>]*)>([\s\S]*?)<\/[^>\n]*invoke>/gi;
|
|
81
|
+
let match = invokePattern.exec(blockText);
|
|
82
|
+
while (match) {
|
|
83
|
+
const attributes = String(match[1] || "");
|
|
84
|
+
const body = normalizeText(String(match[2] || ""));
|
|
85
|
+
const quotedNameMatch =
|
|
86
|
+
attributes.match(/\bname\s*=\s*"([^"]+)"/i) || attributes.match(/\bname\s*=\s*'([^']+)'/i);
|
|
87
|
+
const bareNameMatch = attributes.match(/\bname\s*=\s*([^\s"'/>]+)/i);
|
|
88
|
+
const name = normalizeText(quotedNameMatch?.[1] || bareNameMatch?.[1]);
|
|
89
|
+
if (name) {
|
|
90
|
+
calls.push({
|
|
91
|
+
id: `dsml_tool_call_${calls.length + 1}`,
|
|
92
|
+
name,
|
|
93
|
+
arguments: body && /^[\[{]/.test(body) ? body : "{}"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
match = invokePattern.exec(blockText);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return calls;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function consumeCompletionStream(stream, onText = () => {}, {
|
|
104
|
+
signal, limits, preserveWhitespace = false, requireCallIds = false, callIdPrefix = ""
|
|
105
|
+
} = {}) {
|
|
106
|
+
let assistantText = "";
|
|
107
|
+
let finishReason;
|
|
108
|
+
const toolCallsByIndex = new Map();
|
|
109
|
+
|
|
110
|
+
function checkToolLimits(call, count) {
|
|
111
|
+
if (!limits) return;
|
|
112
|
+
if (limits.maxToolCalls === 0) throw new Error("This conversation has no configured application tools.");
|
|
113
|
+
if (count > limits.maxToolCalls || call.name.length > 256 || call.id.length > 256 ||
|
|
114
|
+
Buffer.byteLength(call.arguments) > limits.maxToolArgumentBytes) {
|
|
115
|
+
throw new Error("The model exceeded the configured application tool-call limits.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for await (const chunk of stream) {
|
|
120
|
+
signal?.throwIfAborted();
|
|
121
|
+
const choice = chunk?.choices?.[0] || {};
|
|
122
|
+
const delta = choice?.delta || {};
|
|
123
|
+
finishReason = choice.finish_reason || finishReason;
|
|
124
|
+
|
|
125
|
+
const textDelta = extractTextDelta(delta.content);
|
|
126
|
+
if (textDelta) {
|
|
127
|
+
assistantText += textDelta;
|
|
128
|
+
if (limits && assistantText.length > limits.maxOutputCharacters) {
|
|
129
|
+
throw new Error("The assistant exceeded the configured response limit.");
|
|
130
|
+
}
|
|
131
|
+
// An opening provider tag can be split between chunks. Keep it private
|
|
132
|
+
// until its boundary is known; complete internal blocks are stripped below.
|
|
133
|
+
const tagStart = assistantText.lastIndexOf("<");
|
|
134
|
+
const safeText = tagStart > assistantText.lastIndexOf(">") ? assistantText.slice(0, tagStart) : assistantText;
|
|
135
|
+
await onText(sanitizeAssistantMessageText(safeText, { preserveWhitespace }));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const toolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
|
|
139
|
+
for (const partialToolCall of toolCalls) {
|
|
140
|
+
const index = Number(partialToolCall?.index || 0);
|
|
141
|
+
const existing =
|
|
142
|
+
toolCallsByIndex.get(index) ||
|
|
143
|
+
{
|
|
144
|
+
id: normalizeText(partialToolCall?.id) || (requireCallIds ? "" : `tool_call_${index + 1}`),
|
|
145
|
+
name: "",
|
|
146
|
+
arguments: ""
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (partialToolCall?.id) {
|
|
150
|
+
existing.id = normalizeText(partialToolCall.id) || existing.id;
|
|
151
|
+
}
|
|
152
|
+
if (partialToolCall?.function?.name) {
|
|
153
|
+
existing.name += String(partialToolCall.function.name || "");
|
|
154
|
+
}
|
|
155
|
+
if (partialToolCall?.function?.arguments) {
|
|
156
|
+
existing.arguments += String(partialToolCall.function.arguments || "");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
toolCallsByIndex.set(index, existing);
|
|
160
|
+
checkToolLimits(existing, toolCallsByIndex.size);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let toolCalls = [...toolCallsByIndex.values()].map((toolCall, index) => ({
|
|
165
|
+
id: normalizeText(toolCall.id) || (requireCallIds ? "" : `tool_call_${index + 1}`),
|
|
166
|
+
name: normalizeText(toolCall.name),
|
|
167
|
+
arguments: String(toolCall.arguments || "")
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
let toolCallSource = toolCalls.length ? "structured" : "";
|
|
171
|
+
if (toolCalls.length < 1) {
|
|
172
|
+
const parsedDsmlCalls = parseDsmlToolCallsFromText(assistantText);
|
|
173
|
+
if (parsedDsmlCalls.length > 0) {
|
|
174
|
+
toolCallSource = "text";
|
|
175
|
+
toolCalls = parsedDsmlCalls.map(call => ({ ...call, id: `${callIdPrefix}${call.id}` }));
|
|
176
|
+
for (const call of toolCalls) checkToolLimits(call, toolCalls.length);
|
|
177
|
+
const visible = sanitizeAssistantMessageText(assistantText, { preserveWhitespace });
|
|
178
|
+
assistantText = preserveWhitespace ? visible : normalizeText(visible);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
assistantText,
|
|
184
|
+
toolCalls,
|
|
185
|
+
toolCallSource,
|
|
186
|
+
finishReason
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { consumeCompletionStream, sanitizeAssistantMessageText };
|