mirror of
https://github.com/hanzochat/agents.git
synced 2026-10-30 20:55:06 +00:00
* fix: dedupe repeated scalar response_metadata in streamed chunks Providers that emit `finish_reason` on more than one streamed chunk (e.g. OpenRouter / custom OpenAI-compatible endpoints) made LangChain core's `_mergeDicts` concatenate the scalar fields `@langchain/openai` stamps alongside finish_reason, producing `finish_reason: "stopstop"` and duplicated `model_name` in both the aggregated graph message and the Langfuse trace (core keeps `id`/`name`/`model_provider` last, but not these). Fold a keep-first dedupe into the shared `delayStreamChunks` wrapper so repeated `model_name`/`finish_reason`/`service_tier`/`system_fingerprint` are dropped at the chunk source, before either the core `_streamIterator` aggregation (handleLLMEnd / Langfuse) or any host-side concat runs. Single occurrences are untouched. Covers ChatOpenAI/Azure/DeepSeek/XAI, and ChatOpenRouter/ChatMoonshot via inheritance. * refactor: move stream metadata dedupe helper to sibling module * fix: dedupe stream metadata before emitting token callbacks Move dropRepeatedScalarMetadata ahead of emitStreamChunkCallback so token callbacks observe the same cleaned chunk as the yielded/aggregated one. Keep-first means the first finish chunk retains its metadata; prompt/completion token indices are untouched. Adds a token-callback assertion to the regression. * fix: scope stream metadata dedupe by completion, clone, and preserve subclass finish signal Addresses Codex P2/P3 findings on the scalar-metadata de-dup: - Key `seen` by completion index so multi-choice (n>1) responses, which `_generate` aggregates per choice, keep each choice's finish metadata. - Clone generationInfo/response_metadata before deleting, so providers that share those objects across split chunks (ChatDeepSeek) don't lose metadata from an already-emitted sibling. - De-duplicate at each terminal emit rather than in the shared super path: delayStreamChunks dedupes only when given a seen-map; ChatOpenAI exposes a raw variant, and ChatOpenRouter consumes it so its finish_reason-driven reasoning flush runs before de-dup. Verified live on openai/gpt-chat-latest and deepseek/deepseek-r1 (reasoning details preserved, metadata de-duped).
126 lines
3.7 KiB
JavaScript
126 lines
3.7 KiB
JavaScript
import { defineConfig, globalIgnores } from "eslint/config";
|
|
import { fixupConfigRules, fixupPluginRules } from "@eslint/compat";
|
|
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
import _import from "eslint-plugin-import";
|
|
import globals from "globals";
|
|
import tsParser from "@typescript-eslint/parser";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import js from "@eslint/js";
|
|
import { FlatCompat } from "@eslint/eslintrc";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const compat = new FlatCompat({
|
|
baseDirectory: __dirname,
|
|
recommendedConfig: js.configs.recommended,
|
|
allConfig: js.configs.all
|
|
});
|
|
|
|
export default defineConfig([globalIgnores([
|
|
"dist/**/*",
|
|
"config/**/*",
|
|
"routes/**/*",
|
|
"**/*.js",
|
|
"**/*.mjs",
|
|
"src/proto/",
|
|
"src/scripts/",
|
|
"types/**/*",
|
|
"./script_docs.ts",
|
|
"src/llm/anthropic/llm.spec.ts",
|
|
"src/llm/anthropic/inherited-strict.spec.ts",
|
|
"src/llm/anthropic/inherited-stream-events.spec.ts",
|
|
"src/llm/anthropic/inherited-content-utils.spec.ts",
|
|
"src/llm/google/llm.spec.ts",
|
|
"src/llm/google/inherited-stream-events.spec.ts",
|
|
"src/llm/openai/llm.spec.ts",
|
|
"src/llm/openai/inherited-deepseek.spec.ts",
|
|
"src/llm/openai/inherited-xai.spec.ts",
|
|
"src/llm/openai/streamMetadataDedup.spec.ts",
|
|
"src/llm/openai/streamMetadata.spec.ts",
|
|
"src/llm/bedrock/llm.spec.ts",
|
|
"src/llm/bedrock/inherited-cache.spec.ts",
|
|
"src/llm/bedrock/inherited.spec.ts",
|
|
"src/llm/vertexai/llm.spec.ts",
|
|
"src/llm/vertexai/inherited-stream-events.spec.ts",
|
|
]), {
|
|
extends: fixupConfigRules(compat.extends(
|
|
"eslint:recommended",
|
|
"plugin:@typescript-eslint/recommended",
|
|
"plugin:import/errors",
|
|
"plugin:import/warnings",
|
|
"plugin:import/typescript",
|
|
)),
|
|
|
|
plugins: {
|
|
"@typescript-eslint": fixupPluginRules(typescriptEslint),
|
|
import: fixupPluginRules(_import),
|
|
},
|
|
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.node,
|
|
},
|
|
|
|
parser: tsParser,
|
|
ecmaVersion: 2021,
|
|
sourceType: "module",
|
|
|
|
parserOptions: {
|
|
project: ["./tsconfig.json", "./scripts/tsconfig.json"],
|
|
},
|
|
},
|
|
|
|
settings: {
|
|
"import/resolver": {
|
|
typescript: {
|
|
alwaysTryTypes: true,
|
|
project: "./tsconfig.json",
|
|
},
|
|
},
|
|
},
|
|
|
|
rules: {
|
|
"no-trailing-spaces": "error",
|
|
indent: ["error", 2],
|
|
"linebreak-style": ["error", "unix"],
|
|
quotes: ["error", "single"],
|
|
semi: ["error", "always"],
|
|
|
|
"no-multiple-empty-lines": ["error", {
|
|
max: 1,
|
|
maxEOF: 0,
|
|
}],
|
|
|
|
"no-console": "warn",
|
|
"prefer-const": "error",
|
|
|
|
"@typescript-eslint/no-unused-vars": ["error", {
|
|
argsIgnorePattern: "^_",
|
|
varsIgnorePattern: "^_",
|
|
caughtErrorsIgnorePattern: "^_",
|
|
destructuredArrayIgnorePattern: "^_"
|
|
}],
|
|
|
|
"@typescript-eslint/consistent-type-assertions": "error",
|
|
"@typescript-eslint/explicit-function-return-type": "error",
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"no-nested-ternary": "error",
|
|
"@typescript-eslint/no-unnecessary-condition": "warn",
|
|
"@typescript-eslint/strict-boolean-expressions": "warn",
|
|
},
|
|
}, {
|
|
files: ["src/stream.ts", "src/utils/logging.ts", "scripts/**/*.ts"],
|
|
|
|
rules: {
|
|
"no-console": "off",
|
|
},
|
|
}, {
|
|
files: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**/*.ts"],
|
|
|
|
rules: {
|
|
"@typescript-eslint/no-require-imports": "off",
|
|
"@typescript-eslint/explicit-function-return-type": "off",
|
|
},
|
|
}]);
|