praxis-agent 0.48.0 → 0.48.1
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/README.md +4 -3
- package/dist/application/session-service.js +6 -2
- package/dist/core/runtime.d.ts +7 -0
- package/dist/core/runtime.js +21 -1
- package/dist/core/transcript-event.js +9 -2
- package/dist/providers/anthropic-compatible.js +11 -3
- package/dist/providers/codex-subscription.js +7 -2
- package/dist/providers/openai-compatible.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,8 +215,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
215
215
|
disconnect recovery that never replays an already-dispatched call.
|
|
216
216
|
- **Provider-neutral models** — native Provider Registry/Vault routing, API
|
|
217
217
|
adapters, an experimental Codex OAuth adapter, explicit capability checks,
|
|
218
|
-
per-attempt bounded deadlines,
|
|
219
|
-
|
|
218
|
+
per-attempt bounded deadlines, typed recovery for malformed streamed tool
|
|
219
|
+
arguments without tool execution or lost resumability, and
|
|
220
|
+
token-only/no-API-dollar accounting for subscription runs.
|
|
220
221
|
- **Transactional self-update** — `praxis update` verifies the package before
|
|
221
222
|
installing it, rejects concurrent updates, and can roll back after an
|
|
222
223
|
interruption or crash.
|
|
@@ -301,7 +302,7 @@ normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
|
301
302
|
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
302
303
|
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
|
|
303
304
|
and rejects any production runtime module with zero covered statements (while allowing
|
|
304
|
-
type-only modules). `npm run test:fixtures` executes the
|
|
305
|
+
type-only modules). `npm run test:fixtures` executes the 69-behavior native contract; 61 behaviors
|
|
305
306
|
are qualified and 8 are explicitly excluded. `npm run verify:fixture-contracts`
|
|
306
307
|
performs the structural check and is part of `npm run check`.
|
|
307
308
|
`npm run test:core-completion` is retained as a compatibility alias for
|
|
@@ -15,7 +15,7 @@ import { projectNativeSessionEntries } from './native-session-projection.js';
|
|
|
15
15
|
import { classifyClaudeInterruption, } from '../native/interruption.js';
|
|
16
16
|
import { findUnresolvedClaudeToolCalls, getClaudeContentBlocks, } from '../native/tool-links.js';
|
|
17
17
|
import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries, createClaudeLastPromptEntry, createClaudeRuleAttachmentEntry, translateProviderEvents, } from '../native/translation.js';
|
|
18
|
-
import { AgentRunCancelledError, AgentRuntime, ModelProviderError, } from '../core/runtime.js';
|
|
18
|
+
import { AgentRunCancelledError, AgentRuntime, MALFORMED_TOOL_INPUT_MESSAGE, ModelProviderError, } from '../core/runtime.js';
|
|
19
19
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
20
20
|
import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
21
21
|
import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
@@ -3347,7 +3347,11 @@ export class ClaudeSessionService {
|
|
|
3347
3347
|
: {}),
|
|
3348
3348
|
};
|
|
3349
3349
|
const unresolvedToolCall = unresolvedToolCalls[0];
|
|
3350
|
-
if (unresolvedToolCall &&
|
|
3350
|
+
if (unresolvedToolCall &&
|
|
3351
|
+
!unresolvedToolCalls.every((call) => call.inputError?.kind === 'malformed_json' &&
|
|
3352
|
+
call.inputError?.message ===
|
|
3353
|
+
MALFORMED_TOOL_INPUT_MESSAGE) &&
|
|
3354
|
+
!approveRecovery) {
|
|
3351
3355
|
throw new Error(`Claude session tool call ${unresolvedToolCall.id} requires explicit recovery approval`);
|
|
3352
3356
|
}
|
|
3353
3357
|
const recoveryResults = await runtime.recoverToolCalls(unresolvedToolCalls, recoveryRequest);
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -43,7 +43,14 @@ export interface ModelToolCall {
|
|
|
43
43
|
id: string;
|
|
44
44
|
name: string;
|
|
45
45
|
input: Record<string, unknown>;
|
|
46
|
+
inputError?: ModelToolInputError;
|
|
46
47
|
}
|
|
48
|
+
export declare const MALFORMED_TOOL_INPUT_MESSAGE = "Malformed tool input";
|
|
49
|
+
export interface ModelToolInputError {
|
|
50
|
+
kind: 'malformed_json';
|
|
51
|
+
message: typeof MALFORMED_TOOL_INPUT_MESSAGE;
|
|
52
|
+
}
|
|
53
|
+
export declare function malformedModelToolCall(id: string, name: string): ModelToolCall;
|
|
47
54
|
export type ModelThinkingMode = 'enabled' | 'adaptive' | 'disabled';
|
|
48
55
|
export type ModelThinkingBlock = {
|
|
49
56
|
type: 'thinking';
|
package/dist/core/runtime.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { ToolExecutionScheduler } from './tool-execution-scheduler.js';
|
|
2
2
|
import { resolveToolSchedulingPolicy } from './tool-scheduling-policy.js';
|
|
3
|
+
export const MALFORMED_TOOL_INPUT_MESSAGE = 'Malformed tool input';
|
|
4
|
+
export function malformedModelToolCall(id, name) {
|
|
5
|
+
return {
|
|
6
|
+
id,
|
|
7
|
+
name,
|
|
8
|
+
input: {},
|
|
9
|
+
inputError: {
|
|
10
|
+
kind: 'malformed_json',
|
|
11
|
+
message: MALFORMED_TOOL_INPUT_MESSAGE,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
3
15
|
const permissionDecisionSources = new WeakMap();
|
|
4
16
|
const autoModePermissionOutcomes = new WeakMap();
|
|
5
17
|
export function annotatePermissionDecision(decision, source) {
|
|
@@ -516,7 +528,12 @@ export class AgentRuntime {
|
|
|
516
528
|
}
|
|
517
529
|
toolCalls.push(event.call);
|
|
518
530
|
this.emit(event);
|
|
519
|
-
const policy =
|
|
531
|
+
const policy = event.call.inputError
|
|
532
|
+
? {
|
|
533
|
+
concurrency: 'exclusive',
|
|
534
|
+
startAfterAssistant: true,
|
|
535
|
+
}
|
|
536
|
+
: resolveToolSchedulingPolicy(this.options.tools, event.call);
|
|
520
537
|
toolScheduler.schedule(event.call, request.observer?.toolExecutionStarted
|
|
521
538
|
? { ...policy, startAfterAssistant: true }
|
|
522
539
|
: policy);
|
|
@@ -863,6 +880,9 @@ export class AgentRuntime {
|
|
|
863
880
|
try {
|
|
864
881
|
if (emitPresentation)
|
|
865
882
|
emitProgress(0);
|
|
883
|
+
if (call.inputError) {
|
|
884
|
+
return { content: call.inputError.message, isError: true };
|
|
885
|
+
}
|
|
866
886
|
const executed = await this.executeTool(call, { ...request, signal }, messages);
|
|
867
887
|
const unsupportedImages = executed.images?.length && this.provider.capabilities.images !== true;
|
|
868
888
|
const unsupportedDocuments = executed.documents?.length &&
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MALFORMED_TOOL_INPUT_MESSAGE } from './runtime.js';
|
|
1
2
|
const terminalReasons = new Set([
|
|
2
3
|
'end_turn',
|
|
3
4
|
'tool_use',
|
|
@@ -51,11 +52,17 @@ export function isModelContentBlock(value) {
|
|
|
51
52
|
return isModelMedia(value);
|
|
52
53
|
}
|
|
53
54
|
function isModelToolCall(value) {
|
|
55
|
+
const inputError = isRecord(value) ? value.inputError : undefined;
|
|
54
56
|
return (isRecord(value) &&
|
|
55
|
-
hasOnlyKeys(value, ['id', 'name', 'input']) &&
|
|
57
|
+
hasOnlyKeys(value, ['id', 'name', 'input', 'inputError']) &&
|
|
56
58
|
isNonEmptyString(value.id) &&
|
|
57
59
|
isNonEmptyString(value.name) &&
|
|
58
|
-
isRecord(value.input)
|
|
60
|
+
isRecord(value.input) &&
|
|
61
|
+
(inputError === undefined ||
|
|
62
|
+
(isRecord(inputError) &&
|
|
63
|
+
hasOnlyKeys(inputError, ['kind', 'message']) &&
|
|
64
|
+
inputError.kind === 'malformed_json' &&
|
|
65
|
+
inputError.message === MALFORMED_TOOL_INPUT_MESSAGE)));
|
|
59
66
|
}
|
|
60
67
|
function isModelThinkingBlock(value) {
|
|
61
68
|
return isRecord(value) && value.type === 'thinking'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProviderError, } from '../core/runtime.js';
|
|
1
|
+
import { ModelProviderError, malformedModelToolCall, } from '../core/runtime.js';
|
|
2
2
|
import { transportFailureKind } from './provider-errors.js';
|
|
3
3
|
import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
|
|
4
4
|
function isRecord(value) {
|
|
@@ -119,8 +119,16 @@ function completedToolCall(state, index) {
|
|
|
119
119
|
try {
|
|
120
120
|
input = JSON.parse(pending.partialJson);
|
|
121
121
|
}
|
|
122
|
-
catch
|
|
123
|
-
|
|
122
|
+
catch {
|
|
123
|
+
if (!pending.id || !pending.name) {
|
|
124
|
+
throw new ModelProviderError('Provider returned an invalid tool call', {
|
|
125
|
+
retryable: false,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
type: 'tool-call',
|
|
130
|
+
call: malformedModelToolCall(pending.id, pending.name),
|
|
131
|
+
};
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
if (!pending.id || !pending.name || !isRecord(input)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProviderError, } from '../core/runtime.js';
|
|
1
|
+
import { ModelProviderError, malformedModelToolCall, } from '../core/runtime.js';
|
|
2
2
|
import { CodexOAuthError, } from './codex-oauth.js';
|
|
3
3
|
export const CODEX_RESPONSES_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';
|
|
4
4
|
const DEFAULT_MAX_STREAM = 1024 * 1024;
|
|
@@ -572,7 +572,12 @@ function parseEvent(value, state, limits) {
|
|
|
572
572
|
parsed = JSON.parse(call.arguments || '{}');
|
|
573
573
|
}
|
|
574
574
|
catch {
|
|
575
|
-
|
|
575
|
+
if (!id || !name)
|
|
576
|
+
throw invalid('Codex provider returned an invalid tool call');
|
|
577
|
+
call.emitted = true;
|
|
578
|
+
state.toolCallEmitted = true;
|
|
579
|
+
events.push({ type: 'tool-call', call: malformedModelToolCall(id, name) });
|
|
580
|
+
return events;
|
|
576
581
|
}
|
|
577
582
|
if (!isRecord(parsed))
|
|
578
583
|
throw invalid('Codex provider returned non-object function arguments');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProviderError, } from '../core/runtime.js';
|
|
1
|
+
import { ModelProviderError, malformedModelToolCall, } from '../core/runtime.js';
|
|
2
2
|
import { transportFailureKind } from './provider-errors.js';
|
|
3
3
|
function isRecord(value) {
|
|
4
4
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -64,8 +64,16 @@ function completedToolCallEvents(pending) {
|
|
|
64
64
|
try {
|
|
65
65
|
input = JSON.parse(call.arguments || '{}');
|
|
66
66
|
}
|
|
67
|
-
catch
|
|
68
|
-
|
|
67
|
+
catch {
|
|
68
|
+
if (!call.id || !call.name) {
|
|
69
|
+
throw new ModelProviderError('Provider returned an invalid tool call', {
|
|
70
|
+
retryable: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
type: 'tool-call',
|
|
75
|
+
call: malformedModelToolCall(call.id, call.name),
|
|
76
|
+
};
|
|
69
77
|
}
|
|
70
78
|
if (!isRecord(input) || !call.id || !call.name) {
|
|
71
79
|
throw new ModelProviderError('Provider returned an invalid tool call', {
|